Metadata-Version: 2.5
Name: enginehall
Version: 2.0.0
Summary: EngineHall: where hardware gets built, checked and released, together. The engine checks that schematic, CubeMX configuration and firmware agree.
Project-URL: Homepage, https://enginehall.com
Author: Newmatik GmbH
License: EngineHall — Proprietary Software License
        
        Copyright (c) 2026 Newmatik GmbH. All rights reserved.
        
        This software and its accompanying documentation, rule packs, schemas, skills,
        and fixtures (the "Software") are the confidential and proprietary property of
        Newmatik GmbH.
        
        NO LICENSE IS GRANTED for this source repository except by a separate written
        agreement signed by Newmatik GmbH. In the absence of such an agreement, you may
        not use, copy, modify, merge, publish, distribute, sublicense, sell, reverse
        engineer, or create derivative works of the Software, in whole or in part.
        
        Copies of the `enginehall` package (formerly `pcba`) and VS Code extension distributed by
        Newmatik through enginehall.com, PyPI, the Visual Studio Marketplace, or Open VSX are offered
        under the end-user license at https://enginehall.com/license. That grant does
        not apply to this source repository.
        
        Unauthorized copying of this file or of this repository, via any medium, is
        strictly prohibited.
        
        NOTICES
        
        1. This repository is NOT open source. Do not add an OSI license file, and do
           not copy code out of it into any open-source project.
        
        2. Related Newmatik projects carry different licenses and are NOT governed by
           this file:
             - gerbtrace .................. BUSL-1.1
             - altium-schdoc-viewer ....... MIT (public)
             - package-renderer ........... UNLICENSED / internal
           Code must not be moved between those projects and this one without a
           deliberate licensing decision. In particular, nothing proprietary from this
           repository may be copied into altium-schdoc-viewer, which is public.
        
        3. This repository must never contain customer design data. Customer design
           output is owned by the customer, not by Newmatik, and may not be
           redistributed as part of this Software. See AGENTS.md.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
        FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL NEWMATIK GMBH BE
        LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
        CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
        SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
        
        EngineHall reports findings to assist human engineering review. It is not a
        certification tool and does not constitute electrical sign-off.
        
        Newmatik GmbH
        https://enginehall.com
License-File: LICENSE
Keywords: altium,cubemx,embedded,firmware,review,schematic,stm32
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
Requires-Python: >=3.11
Requires-Dist: olefile>=0.46
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: jsonschema>=4.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# EngineHall

*Where hardware gets built, checked and released, together.* A Newmatik product, formerly PCBA Studio.

EngineHall is the shared workspace of a hardware development project on [enginehall.com](https://enginehall.com):
open points, meetings, decisions, documentation, design reviews and releases, for a team and its development
partners. Its engine, described below, runs on your machine. It installs as the `enginehall` package; the commands
are `enginehall` and `hall` (`pcba` still works for one major version and says it is deprecated).

EngineHall's engine finds contradictions between an Altium or KiCad schematic, an STM32CubeMX
configuration, and firmware. A board can route one pin, CubeMX can configure another, and the code
can still compile; EngineHall checks those boundaries before they become bench failures. On a KiCad
project it also verifies the schematic, the parts and the board: KiCad's own ERC and DRC, rails,
decoupling, pull-ups, supply ranges and derating against component data, connector pinouts, the
Newmatik house DFM values, and declared SPICE testbenches.

The Python engine is the product. It runs locally, uploads nothing, and produces evidence-backed
findings in text, Markdown, JSON, or SARIF. The VS Code extension is an optional UI for running the
engine, viewing `.SchDoc` files, and keeping a Markdown project tracker. The public site lives in
`site/` and is published at [enginehall.com](https://enginehall.com).

## Quickstart

Requirements: Python 3.11–3.14. Developing this repo also needs Node: 20, 22, or 24 for the extension and the pre-commit hook,
and Node 24.18 or a later 24.x with pnpm for the site.
From this source checkout:

```sh
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
```

For a public install without a source checkout, use the checked wheel served by the site:

```sh
python -m pip install https://enginehall.com/engine/enginehall-2.0.0-py3-none-any.whl
```

The PyPI listing is pending. The install page will switch to `enginehall==2.0.0` after
that listing is verified.

Confirm what EngineHall can see, then run a review:

```sh
enginehall inspect .
enginehall review .
```

Try the synthetic reference board with **Try the reference board** at
[enginehall.com/review](https://enginehall.com/review). It intentionally reports six injected
errors. Exit status `0` means the selected
failure threshold was clean, `1` means findings met it, and `2` means a required check could not
run or the review could not run.
Always read `coverage.skipped`, the compared-signal count, and
`coverage.statistics.uncompared_signals`; zero findings only describes the checks and signal
pairs the review could evaluate. Each unpaired CubeMX placement now names its pin and reason.

## Project setup

For a single target, discovery finds the directory with the most `.SchDoc` files, the first `.ioc`,
firmware roots containing `Core/Src`, and the MCU part. Use explicit flags if discovery is ambiguous:

```sh
enginehall review . --schematic-dir hardware --ioc config/board.ioc --part YOUR_STM32_PART
```

`--ioc` and `--schematic-dir` are read relative to the project root (here `.`), then the working
directory. A path that exists in neither stops the review with exit `2` instead of quietly
falling back to discovery. When discovery finds several firmware trees, it keeps the ones beside
the `.ioc` and says so in `project.notes`; name others with `firmware_roots` in
`pcba-studio.toml`.

The alternate-function rule reads STM32CubeMX's MCU database (`db/mcu`) from the usual install
locations. Set the `PCBA_CUBEMX_DB` environment variable to use another copy, for example the same
one on every machine; without a database the rule is listed in `coverage.skipped`.

If the sheets hold more than one instance of the MCU, the pin map is not extracted until you name
one; an unannotated designator such as `U?` never selects an instance:

```sh
enginehall review . --schematic-component U2
```

For several boards or firmware targets, add `pcba-studio.toml` at the project root:

```toml
version = 1
default_target = "controller"

[targets.controller]
part = "YOUR_ORDERABLE_STM32_PART"
schematic_component = "U1"
ioc = "config/controller.ioc"
schematic_dir = "hardware/controller"
firmware_roots = ["firmware/controller"]

[targets.interface]
ioc = "config/interface.ioc"
schematic_dir = "hardware/interface"
firmware_roots = ["firmware/interface"]
```

Paths must exist and remain inside the project. Select a non-default target with `--target`:

```sh
enginehall inspect . --target interface
enginehall review . --target interface
```

### KiCad projects

`enginehall review` finds a `.kicad_pro` like it finds `.SchDoc` sheets; `--kicad-project` (or
`kicad_project` on a target) chooses one. `kicad-cli` and `ngspice` are optional: without them,
ERC, DRC, the netlist cross-check and the testbenches are listed as not checked. An optional
`pcba-requirements.toml` declares rail voltages, derating limits, single-sided assembly, edge
clearance, connector pinouts and SPICE testbenches. Every rule ends as passed, failed, info, not
checked (with the reason) or not applicable in `coverage.rules`. Details:
[`docs/kicad-review.md`](docs/kicad-review.md).

```sh
enginehall doctor --no-ping                  # kicad-cli and ngspice found?
enginehall review boards/main --format markdown
enginehall kicad netlist boards/main         # the nets the engine built from the wires
```

### Requiring coverage

"No findings" from a check that never ran is not a pass. Name the checks that must have usable
input; if one could not run, or compared no signal at all, the review exits `2`:

```sh
enginehall review . --require-rule cross_domain.pin.mismatch --require-rule cross_domain.firmware.hardcoded_pin_drift
```

A signal on several pins of one side is not compared first-wins; the report lists it as not compared.
Placements are compared by pad as well as by name, so a board whose nets carry functional names
(`RMII_TXD0`, `WIFI_SPI_SCK`) is still compared pin by pin.
The browser review accepts the same `pcba-studio.toml`, target, MCU overrides, baseline, and
required-rule inputs. Select a project folder to keep configured paths intact. Local Git
`--changed-only` and the installed CubeMX alternate-function database remain CLI-only.

### Existing findings

Create an auditable baseline when adopting the checker in an established project:

```sh
enginehall baseline . --reason "Reviewed during initial adoption"
enginehall review . --baseline .pcba-baseline.json
```

The baseline stores stable finding IDs and a required reason for every suppression. Reviews report
both the number suppressed and stale IDs that no longer match a finding. Review and commit the file;
do not use it to hide new defects.

A `cross_domain.firmware.hardcoded_pin_drift` ID is derived from the `.ioc`, the pin, and the pin's
`.ioc` key, not from the first source file that names the pin, so adding another source file no
longer changes it. Baselines written by earlier versions suppress this rule under the old
file-based ID: regenerate the baseline once, or the old entry is reported as stale and the finding
reappears.

### Changed files and pull requests

Limit local output to findings whose location or evidence touches a changed file:

```sh
enginehall review . --changed-only origin/main
```

The source repository is private, so public repositories install the checked wheel from
enginehall.com in CI, exactly as the [documentation](https://enginehall.com/docs#ci) shows. Switch
the install line to `enginehall==2.0.0` once the PyPI listing is published. Keep full Git history when
using `--changed-only`:

```yaml
permissions:
  contents: read

steps:
  - uses: actions/checkout@v7
    with:
      fetch-depth: 0
  - uses: actions/setup-python@v7
    with:
      python-version: "3.12"
  - run: python -m pip install https://enginehall.com/engine/enginehall-2.0.0-py3-none-any.whl
  - run: enginehall review . --require-rule cross_domain.pin.mismatch
```

The composite action in this private repository remains available to Newmatik projects. It runs a
changed-only review, can upload SARIF, and preserves findings even when required coverage is
incomplete. Add the firmware rule to public CI when the repository has firmware.

### Publishing to a development project

`--publish` sends the report (findings, their locations and evidence, and coverage) to the check
runs of a development project on the site. No design file or source file is sent, and local paths
are made project-relative. A lead creates the project token under Members, Tokens; pass it in an
environment variable, never on the command line:

```sh
PCBA_WORKSPACE_TOKEN=... enginehall review . --publish --project DEMO --url https://enginehall.com
```

The run carries its commit and repository, so a release drafted from it records the commit:
`--commit` defaults to `GITHUB_SHA`, else `git rev-parse HEAD` in the reviewed folder, and
`--repository` (a repository id, or its URL) defaults to the `origin` remote, sent as
`https://host/path` without credentials. The site matches the URL against the project's connected
repositories; one it does not know is noted, not an error, and a folder without git publishes
without either. `--token-env NAME` reads another variable. The exit codes
stay the same, except that a failed publish exits `2`. The action's `publish`, `project` and
`workspace-url` inputs do the same, with `PCBA_WORKSPACE_TOKEN` set from a secret in the step's
`env`.

## What it checks

| Rule | Detects |
| --- | --- |
| `cross_domain.mcu.part_mismatch` | The schematic and CubeMX file identify different MCU parts; pin comparisons are skipped until the inputs match. |
| `cross_domain.pin.mismatch` | A peripheral signal routed to different pins in the schematic and `.ioc`. |
| `cross_domain.pin.signal_conflict` | A pad whose schematic net names a different peripheral than the `.ioc` assigns to it (SPI2_MOSI on a pin configured as I2C1_SDA). |
| `cross_domain.firmware.hardcoded_pin_drift` | Hand-written code that writes, configures, or otherwise names a pin now assigned to a peripheral in the `.ioc`. Reads (`HAL_GPIO_ReadPin`) do not count. |
| `cross_domain.pin.unconfigured` | A schematic peripheral signal absent from the `.ioc`. |
| `cross_domain.mcu_config.incomplete` | A placeholder or wrong `.ioc` covering almost none of the schematic. |
| `cross_domain.peripheral.unconnected` | A configured peripheral with no matching schematic connection. |
| `schematic.net.mislabelled` | A label whose pin prefix disagrees with geometric connectivity. |
| `schematic.net.open_circuit` | A part that nearly touches an MCU pin but stops short. This rule is heuristic. |
| `stm32.pin.alternate_function_impossible` | A peripheral signal the selected pin cannot carry according to ST's MCU database. |

On KiCad projects (see [`docs/kicad-review.md`](docs/kicad-review.md) for what each needs):

| Rule | Detects |
| --- | --- |
| `kicad.erc.violation` | A violation KiCad's ERC reports (kicad-cli). |
| `kicad.netlist.parity` | The engine's geometric connectivity disagrees with KiCad's netlist export (kicad-cli). |
| `kicad.power.rail_without_source` | A net with power inputs but no power output or PWR_FLAG. |
| `kicad.power.pin_without_dc_path` | An IC power pin that reaches no rail through a DC path (only through a capacitor, or not at all). |
| `kicad.power.decoupling_missing` | An IC supply net with no capacitor to ground. |
| `kicad.part.shorted` | A two-pin part with both pins on one net. |
| `kicad.pin.input_floating` | An input pin connected to nothing and not marked no-connect. |
| `kicad.net.pull_up_missing` | An I2C or open-drain net without a pull-up. |
| `kicad.label.global_single_sheet` | A global label used on one sheet only (info). |
| `kicad.label.single_connection` | A label whose net reaches at most one pin. |
| `kicad.net.name_hygiene` | `VBUS` without USB, a rail without a voltage in its name (info). |
| `kicad.component.supply_range` | A supply pin's rail outside the part's operating range or absolute maximum. |
| `kicad.component.capacitor_derating` | A capacitor above its derated voltage rating. |
| `kicad.component.resistor_power` | A resistor between two rails above its derated power rating. |
| `kicad.component.logic_level` | Driver levels a receiver does not recognise, or an input driven past its rating. |
| `kicad.component.required_connection` | A pin without the connection its component data requires (NRST capacitor, enable not floating, pull-ups). |
| `kicad.connector.pin_assignment` | A connector pin that does not carry the signal its interface spec names. |
| `kicad.drc.violation` | A violation or unconnected item KiCad's DRC reports (kicad-cli). |
| `kicad.drc.schematic_parity` | Board and schematic out of step (kicad-cli). |
| `kicad.dfm.fiducials` | House fiducials missing or wrong (NM-FID-001..006). |
| `kicad.dfm.track_angle` | A track not at 0, 45 or 90 degrees (NM-ROUTE-001). |
| `kicad.dfm.via_in_pad` | A via in or touching a pad (NM-VIA-001). |
| `kicad.dfm.via_spacing` | Two same-net vias closer than the house distance (NM-VIA-002). |
| `kicad.dfm.model_missing` | A footprint without a STEP model on disk (NM-3D-001). |
| `kicad.dfm.silkscreen_outside` | Silkscreen beyond the board outline (NM-SILK-001). |
| `kicad.dfm.barcode_field` | No or too small barcode field (NM-BC-001). |
| `kicad.dfm.bottom_side_smd` | An SMD part on the bottom of a board declared single-sided. |
| `kicad.dfm.smd_edge_clearance` | SMD copper closer to the edge than the declared clearance. |
| `kicad.dfm.test_point_coverage` | Nets without a test point. |
| `kicad.dfm.courtyard_overlap` | Overlapping courtyards. |
| `simulation.spice.testbench` | A declared ngspice testbench outside its pass range. |

Every finding has a project-relative location, quoted evidence, consequence, recommendation, rule
ID, severity, and detector classification. JSON output follows
the [public finding schema](https://enginehall.com/schemas/finding.schema.json).

## Architecture

```text
artifact discovery / target config
              |
              v
  .SchDoc + .ioc + firmware  ->  normalized project model
              |                            |
              v                            v
           parsers  --------------------> rules
                                             |
                            baseline / changed-file filter
                                             |
                                             v
                              text / Markdown / JSON / SARIF
```

- `pcba/review.py` is the shared orchestration layer used by the CLI and engine MCP server.
- `pcba/parsers/` reads Altium OLE2 streams, CubeMX `.ioc` files, ST MCU metadata, and hand-written
  firmware references.
- `pcba/rules/` compares the normalized model. Schematic connectivity comes from geometry; labels
  only name nets.
- `pcba/report/` renders stable machine and human formats.
- `extension/` contains the optional VS Code UI, its heuristic viewer parser, the Markdown tracker,
  and a separate single-artifact/tracker MCP server.

The engine parser is authoritative for review findings. The TypeScript parser serves interactive
viewing; both parsers run the same conformance corpora (record streams, placed-pin selection) to
prevent format drift.

## Common commands

```sh
enginehall inspect .                              # artifacts, selected target, and rule coverage
enginehall review .                               # all applicable checks
enginehall review . --format json                 # text, markdown, json, or sarif
enginehall review . --fail-on warning             # choose the failing severity threshold
enginehall baseline . --reason "Reviewed"         # write .pcba-baseline.json atomically
enginehall altium pinmap --dir hardware           # geometric MCU pin-to-net map
enginehall kicad netlist boards/main              # KiCad nets built from wire geometry
enginehall kicad pinmap boards/main               # KiCad MCU pin-to-net map
enginehall altium project-sheets --project Synthetic.PrjPcb --format json  # structural sheet inventory only
enginehall cubemx pins board.ioc                  # CubeMX pin assignments
enginehall cubemx af board.ioc PA2                # allowed alternate functions
enginehall cubemx diff old.ioc new.ioc            # compare generated configurations
enginehall cubemx validate board.ioc manifest.json
enginehall mcp                                    # engine MCP server over stdio
```

Extension development is separate and optional:

```sh
cd extension
npm ci
npm test
npm run package
```

Open `extension/` in VS Code and press F5. The viewer and tracker work without the Python package;
the `EngineHall: Run Hardware/Firmware Review` command requires `pcba` on the extension host's
`PATH`. Findings appear in the Problems panel.

To register the engine with an MCP client, activate the virtualenv and use:

```json
{
  "mcpServers": {
    "enginehall": { "command": "enginehall", "args": ["mcp"] }
  }
}
```

The local servers (`enginehall mcp`, `enginehall altium mcp`, and the extension's) review design files on your
computer. A hosted server at `https://enginehall.com/mcp` lets an assistant read your account and
the saved projects of your workspaces, and read and draft items in your development projects
(every write is previewed and needs your approval); it never receives design files:

```sh
claude mcp add --transport http pcbastudio https://enginehall.com/mcp
```

Setup for other clients, the tool list, and the safety model: <https://enginehall.com/docs/mcp>.

### Claude Code plugin

The repository is also a Claude Code plugin marketplace (`.claude-plugin/`). For anyone with
access to the repository:

```text
/plugin marketplace add newmatik/pcbastudio
/plugin install enginehall@newmatik
```

The plugin brings the seven skills and three MCP servers: `pcba` (review), `pcba-altium` (Altium
drive, Windows with Altium running) and `fusion` (Autodesk Fusion's local server on port 27182, or
`FUSION_MCP_PORT`). The Python servers run with `uvx` from the plugin itself, so
[uv](https://docs.astral.sh/uv/) is the only requirement: no virtualenv, nothing on `PATH`. The
plugin has no version number, so every commit on `main` is an update
(`claude plugin update enginehall@newmatik`). Try a working tree with `claude --plugin-dir .`.
Claude Code hides a plugin server that duplicates one you registered yourself, so remove an
earlier `fusion` or `pcba` entry (`claude mcp remove fusion -s user`) to use the plugin's.
The extension's schematic and tracker server ships with the extension: run **EngineHall: Set up
MCP server for AI chat** in VS Code or Cursor.

## Contributing

### Codex and Claude Code skills

From this source checkout, install the six maintained skills for both clients:

```sh
python3 tools/install-skills.py
# Or select just one client:
python3 tools/install-skills.py --clients codex
```

The installer links into each client's user skill directory.
With the Claude Code plugin installed, link for Codex only (`--clients codex`); Claude Code
would otherwise list every skill twice.
It repairs links after moving the checkout and leaves real directories untouched. Skills stay
in this repository, so later edits are picked up without copying them again. On Windows,
creating directory symlinks requires Developer Mode or an elevated terminal.

Codex MCP configuration uses TOML; `.mcp.json` is the Claude Code configuration. Register
the review engine with an absolute virtualenv executable so GUI launches work:

```sh
codex mcp add pcba -- /absolute/venv/bin/python -m pcba.cli mcp
codex mcp add enginehall-extension -- /absolute/node /absolute/extension/dist/mcp-server.js
codex mcp add fusion --url http://127.0.0.1:27182/mcp
```

The extension bundle provides schematic viewing and tracker tools. Build it with
`cd extension && npm run build` first. The engine and extension have separate tool sets.
On Windows, register Altium's separate drive server with the virtualenv's Python:

```powershell
codex mcp add pcba-altium -- C:\absolute\venv\Scripts\python.exe -m pcba.cli altium mcp
```

Start a new client session after registering servers or skills. Altium editing requires the
MCP process to run in the logged-in Windows desktop session with Altium running. A Mac can
load its tool catalogue, but cannot drive Altium locally; an SSH service session also lacks
the interactive desktop. `enginehall doctor` reports the local prerequisites and connection fixes.

Read `AGENTS.md` in the source checkout before changing code. In particular:

- Never commit customer schematics, `.ioc` values, net labels, part numbers, pin maps, or project
  names. Extend `fixtures/synthetic/` instead.
- Treat `.ioc` files as CubeMX-owned. Any write must use the adapter's inspect, plan, preview,
  approve, apply, reinspect, verify, and record workflow.
- Add deterministic parsing and rules before explanation. Give every finding navigable evidence.
- Add a synthetic defect and exact golden assertion for a new rule. Run `tools/precommit.sh` before
  every commit, and keep one logical change per English commit.

The site (`site/`) runs locally on port 3090, its Worker preview on 8790, and its local Supabase
stack on 60321–60324; `AGENTS.md` lists the commands.

The supported product path is STM32 firmware with Altium or KiCad schematics. EngineHall assists review; it
does not provide certification or engineering sign-off. See `docs/roadmap.md` in the source for
scope and non-goals.

Proprietary software, © 2026 Newmatik GmbH. See the [license](https://enginehall.com/license).
