eda is a command-line toolkit for circuit design — datasheets, SPICE
simulation, KiCad schematic and board review, fabrication output — implemented
as the eda_toolkit Python package and executed inside a container.
This file is the working agreement for anyone changing the code: humans, and
coding agents alike. (CLAUDE.md points here; there is one copy, not two.)
kicad-cli, ngspice or the eda CLI on the host. Use
./bin/eda.sh <command> — it builds and runs the container, maps your uid, and
keeps the host clean. Anything else pollutes the host or fails outright../bin/eda.sh are relative to the repository root (mounted at
/work in the container).KICAD_VERSION selects the KiCad release; the default is in bin/eda.sh and
the Makefile. Keep the two in sync when changing it.make build # build (or rebuild) the image
make test # full suite in the container - this is the gate
make test-host # fast pure-python subset (needs .venv with the test extra)
make smoke # end-to-end run of every top-level command
make test mounts the working tree and puts /work/src on PYTHONPATH, so it
tests the code you are editing, not the copy baked into the image. Rebuild the
image only when the Dockerfile or the dependencies change.
CI runs the suite against every KiCad version in the ci.yml matrix
(currently 10.0.4 and 9.0.9). Run make test KICAD_VERSION=9.0.9 before
pushing anything that touches kicad_cli.py, the fixtures or the review rules.
Not every flag exists in every release: gate on kicad_cli.supports([...],
"--flag") rather than on a version number, and keep the fixtures in the oldest
format the matrix covers — KiCad never reads a file newer than itself.
Rules are functions registered with the @rule decorator in
src/eda_toolkit/kicad/sch_review.py or pcb_review.py. They receive a context
(parsed design + netlist/board + ERC/DRC output) and return Finding objects.
Add the rule, then add a test in tests/test_sch_review.py /
tests/test_pcb_review.py using the in-memory ReviewContext.from_netlist /
PcbContext.from_board constructors — no filesystem needed.
Keep severities honest: error = the design is broken, warning = a human must
judge it, info = context.
tools/review_demos.py runs both reviews over the 18 KiCad demo projects in the
image and aggregates the findings per rule. Use it before and after changing a
rule: a rule that fires thousands of times across that corpus is noise, however
correct each instance is. Findings that repeat more than COLLAPSE_LIMIT times
are folded into one entry by util.collapse_findings, so prefer grading a rule
(warning vs info) over deleting it.
pyproject.toml, then make lock (uv lock).
uv.lock is the only lock file — it carries the hashes and the image installs
it with uv sync --frozen. Do not add a second requirements file.[build-system] requires is resolved outside uv.lock, so it
is pinned with == in pyproject.toml and mirrored by wheel hash in
docker/build-backend.txt. Change both together, and keep the requirement list
free of transitive dependencies — --require-hashes needs every one of them
pinned too, which is why wheel is not listed.<version> <sha256 digest> to docker/kicad-digests.txt
before building with a new KICAD_VERSION. Keep the Dockerfile’s default
KICAD_VERSION/KICAD_DIGEST, the Makefile and bin/eda.sh in agreement,
and add the version to the ci.yml matrix if it is meant to be supported.uses: owner/action@<40-char-sha> # vX.Y.Z. No tag-only refs,
no ubuntu-latest. When a bot proposes a bump, verify the SHA really is that
tag (git ls-remote --tags https://github.com/<owner>/<action>) before merging.tests/fixtures/example_project is a small, DRC-clean KiCad project (RC filter
lib_footprint_mismatch for each of
them. Keep the project clean: a new error there means the toolkit changed
behaviour. Its own README documents the two constraints that are easy to break
by accident — no KiCad 10 only tokens, and the ground pour stays filled.README.md — what the toolkit is and how to use it.docs/guides/ — one usage guide per area, and the source of truth for how
to use the toolkit well. Read the one that matches the task before doing it:
datasheet-analysis, spice-simulation, kicad-schematic-review,
kicad-pcb-review, kicad-fabrication-output, eda-environment.docs/examples/ — committed sample output, regenerable with the commands
documented there.When behaviour changes, update the guide that covers it in the same commit. A guide that describes a flag the CLI no longer has is worse than no guide.
These same files are the website: GitHub Pages serves main / (root) with the
settings in _config.yml — no build workflow, no gh-pages branch, nothing
generated. That puts three constraints on anything published (README.md,
AGENTS.md, docs/**), all enforced by tests/test_docs.py:
# heading. The page title comes from the first
heading, and only if nothing precedes it.--format argument that way. There is no per-file opt-out on the Jekyll that
Pages runs, and the escape hatch would itself show up verbatim on github.com,
so far every case has had a clean alternative.src/, tests/,
docker/ and friends are excluded from the site, so a relative link to them
resolves on github.com and 404s on the site.make site renders it locally with the same pinned gem set Pages uses, which is
how those three were found in the first place.
bin/install-skills.sh renders docs/guides/ into Claude Code’s skill layout
(.claude/skills/<name>/SKILL.md, symlinks, git-ignored). Never edit that copy:
it is generated, and make skills regenerates it.