AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
KiCad 회로·PCB 파일을 파싱·조작하는 독립 파이썬 도구
Standalone Python tools for parsing and manipulating KiCad schematic and PCB files
개발 도구Python · ShellGitHub @rjwalters직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Tools for AI agents to work with KiCad projects.
🌐 Live demo gallery: kicad-tools.org — explore example boards built end-to-end by these tools, with 2D/3D renders, routing & manufacturing metrics, downloadable fabrication packages, and an interactive in-browser PCB viewer.
This project provides standalone Python tools that enable AI agents (LLMs, autonomous coding assistants, etc.) to parse, analyze, and manipulate KiCad schematic and PCB files programmatically. All tools output machine-readable JSON and require no running KiCad instance.README 전문 보기 ↗Tools for AI agents to work with KiCad projects.
🌐 Live demo gallery: kicad-tools.org — explore example boards built end-to-end by these tools, with 2D/3D renders, routing & manufacturing metrics, downloadable fabrication packages, and an interactive in-browser PCB viewer.
This project provides standalone Python tools that enable AI agents (LLMs, autonomous coding assistants, etc.) to parse, analyze, and manipulate KiCad schematic and PCB files programmatically. All tools output machine-readable JSON and require no running KiCad instance.
Traditional EDA tools require GUIs and manual interaction. kicad-tools bridges the gap by providing:
--help for supported formatsWhether you're building an AI assistant that reviews PCB designs, automating DRC checks in CI, or experimenting with LLM-driven routing, these tools provide the foundation.
# Base install (CPU only)
pip install kicad-tools
# With CUDA GPU acceleration (NVIDIA GPUs on Linux/Windows)
pip install kicad-tools[cuda]
# With Metal GPU acceleration (Apple Silicon Macs)
pip install kicad-tools[metal]
# With native C++ router backend
pip install kicad-tools[native]
# With CMA-ES placement optimization (kct optimize-placement / kct build --optimize-placement)
pip install kicad-tools[placement]
# Everything (all optional dependencies)
pip install kicad-tools[all]
The
placementextra pulls incmaes(and, transitively,scipy). It is required bykct optimize-placementand the placement step ofkct build --optimize-placement; without it those commands exit with a clear message naming the extra. Thedevandallextras already include it, souv sync --extra devis sufficient for development.
To check GPU acceleration status:
kct calibrate --show-gpu
The supported setup for a PCB-design repo that depends on kicad-tools is PyPI
plus a committed uv.lock: pyproject.toml carries a lower bound
(kicad-tools[<extras>]>=X) and uv.lock is the exact pin. Install it, with
the agent skills, CI gates and conventions, from a kicad-tools checkout:
./scripts/install-kct.sh /path/to/board-repo # PyPI floor (default; same as --pypi)
./scripts/install-kct.sh --upgrade-workflow /path/to/board-repo # + weekly upgrade PR
cd /path/to/board-repo && uv lock && uv sync && uv run kct build-native
The installer keeps the repo's existing extras and removes any
[tool.uv.sources] kicad-tools entry. Re-running it is a no-op when the floor
is already current. --upgrade-workflow adds .github/workflows/kct-upgrade.yml,
which opens a PR for each new PyPI release; the PR raises the floor, refreshes
uv.lock and the vendored files, and lists the release's upgrade notes.
kct doctor checks that the vendored files match the installed version.
Git and SHA pins (--tag vX.Y.Z, --rev <sha>) and local checkouts
(--path ../kicad-tools) are escape hatches for short-lived use. If you need
a fix that is not on PyPI yet, ask for a patch release rather than carrying a
SHA pin. Run ./scripts/install-kct.sh --help for every option.
kct)# List symbols in a schematic
kct symbols project.kicad_sch
kct symbols project.kicad_sch --format json
# Trace nets
kct nets project.kicad_sch
kct nets project.kicad_sch --net VCC
# Generate bill of materials
kct bom project.kicad_sch
kct bom project.kicad_sch --format csv --group
# Run ERC (requires kicad-cli)
kct erc project.kicad_sch
kct erc project.kicad_sch --errors-only
# Run DRC with manufacturer rules
kct drc board.kicad_pcb
kct drc board.kicad_pcb --mfr jlcpcb
kct mfr compare # Compare manufacturer rules
from kicad_tools import load_schematic, Schematic
# Load and parse a schematic
doc = load_schematic("project.kicad_sch")
sch = Schematic(doc)
# Access symbols
for symbol in sch.symbols:
print(f"{symbol.reference}: {symbol.value}")
# Access hierarchy
for sheet in sch.sheets:
print(f"Sheet: {sheet.name}")
from kicad_tools.router import Autorouter, DesignRules
# Configure design rules
rules = DesignRules(
grid_resolution=0.25, # mm
trace_width=0.2, # mm
trace_clearance=0.15, # mm
)
# Create router and add components
router = Autorouter(width=100, height=80, rules=rules)
router.add_component("U1", pads=[{"number": "1", "x": 10, "y": 10, "net": 1}])
router.add_component("U2", pads=[{"number": "1", "x": 20, "y": 10, "net": 1}])
# Route all nets
routes = router.route_all(timeout=30)
print(f"Created {len(routes)} routes")
The reasoning module enables LLMs to make strategic PCB layout decisions while tools handle geometric execution:
from kicad_tools import PCBReasoningAgent
# Load board
agent = PCBReasoningAgent.from_pcb("board.kicad_pcb")
# Reasoning loop
while not agent.is_complete():
# Get state as prompt for LLM
prompt = agent.get_prompt()
# Call your LLM (OpenAI, Anthropic, local, etc.)
command = call_llm(prompt)
# Execute and get feedback
result, diagnosis = agent.execute(command)
# Save result
agent.save("board_routed.kicad_pcb")
CLI usage:
# Export state for external LLM
kct reason board.kicad_pcb --export-state
# Interactive mode
kct reason board.kicad_pcb --interactive
# Auto-route priority nets
kct reason board.kicad_pcb --auto-route
See examples/llm-routing/ for complete examples.
Enable AI assistants like Claude to interact with KiCad designs via the Model Context Protocol:
# Install with MCP support
pip install "kicad-tools[mcp]"
# Run the MCP server
kct mcp serve
Register the server with your agent harness. kct mcp setup finds the
absolute kct path, merges into the existing config without touching other
servers, and is a no-op when re-run (--dry-run previews, --format json for
scripts):
kct mcp setup --client claude-code # ~/.claude.json via `claude mcp add-json` (default; --scope project for ./.mcp.json)
kct mcp setup --client claude-desktop # Claude Desktop config
kct mcp setup --client codex # [mcp_servers.kct] in $CODEX_HOME/config.toml
kct mcp setup --client opencode # ~/.config/opencode/opencode.json (--project for ./opencode.json)
Or configure Claude Desktop by hand (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"kicad-tools": {
"command": "python",
"args": ["-m", "kicad_tools.mcp.server"]
}
}
}
Available MCP tools:
board_summary, get_drc_violations, measure_clearanceexport_gerbers, export_bom, export_assemblyplacement_analyze, placement_suggestionsstart_session, query_move, apply_move, commit, rollbackroute_net, route_net_auto, get_unrouted_netsoptimize_placement, evaluate_placementSee docs/mcp/ for complete documentation.
Build schematics using reusable, tested circuit blocks:
from kicad_tools.schematic.models import Schematic
from kicad_tools.schematic.blocks import (
MCUBlock,
CrystalOscillator,
LDOBlock,
USBConnector,
DebugHeader,
I2CPullups,
ResetButton,
)
sch = Schematic("My STM32 Design")
# Add an MCU with bypass capacitors
mcu = MCUBlock(
sch, x=150, y=100, part="STM32F103C8T6", bypass_caps=["100nF", "100nF", "100nF", "4.7uF"]
)
# Add crystal oscillator
xtal = CrystalOscillator(sch, x=100, y=100, frequency="8MHz", load_caps="20pF")
# Add power supply
ldo = LDOBlock(sch, x=50, y=100, input_voltage=5.0, output_voltage=3.3)
# Add USB connector with ESD protection
usb = USBConnector(sch, x=50, y=150, connector_type="type-c", esd_protection=True)
# Add debug header for programming
debug = DebugHeader(sch, x=200, y=100, interface="swd")
# Add I2C pull-ups
i2c = I2CPullups(sch, x=180, y=150, pullup_value="4.7k")
# Add reset button with debounce
reset = ResetButton(sch, x=120, y=50, debounce_cap="100nF")
# Connect via ports
sch.add_wire(ldo.port("VOUT"), mcu.port("VDD"))
sch.add_wire(xtal.port("OUT"), mcu.port("OSC_IN"))
sch.save()
Available blocks:
See boards/04-stm32-devboard/ for a complete design example.
Work with complete KiCad projects using the unified Project class:
from kicad_tools import Project
# Load a KiCad project
project = Project.load("myboard.kicad_pro")
# Cross-reference schematic to PCB
result = project.cross_reference()
print(f"Unplaced components: {result.unplaced}")
# Export manufacturing files
project.export_assembly("output/", manufacturer="jlcpcb")
Monitor long-running operations with progress callbacks:
from kicad_tools import ProgressCallback, ProgressContext
from kicad_tools.router import Autorouter
def on_progress(progress: float, message: str, cancelable: bool) -> bool:
print(f"{progress * 100:.0f}%: {message}")
return True # Return False to cancel
# Use with context manager
with ProgressContext(on_progress):
router = Autorouter(...)
router.route_all() # Progress reported automatically
# Or create JSON-formatted callbacks for automation
from kicad_tools import create_json_callback
callback = create_json_callback()
Create KiCad footprints programmatically with IPC-7351 naming:
from kicad_tools.library import create_soic, create_qfp, create_chip
# Generate SOIC-8 footprint
fp = create_soic(pins=8, pitch=1.27)
fp.save("SOIC-8.kicad_mod")
# Generate LQFP-48
fp = create_qfp(pins=48, pitch=0.5, body_size=7.0)
fp.save("LQFP-48.kicad_mod")
# Generate 0402 chip resistor
fp = create_chip("0402", prefix="R")
fp.save("R_0402.kicad_mod")
Available generators: create_soic, create_qfp, create_qfn, create_sot, create_chip, create_dip, create_pin_header.
Create and edit KiCad symbol libraries programmatically:
from kicad_tools.schema.library import SymbolLibrary
# Create a new symbol library
lib = SymbolLibrary.create("myproject.kicad_sym")
# Create a symbol with pins
sym = lib.create_symbol("MyPart")
sym.add_pin("1", "VCC", "power_in", (0, 5.08))
sym.add_pin("2", "GND", "power_in", (0, -5.08))
sym.add_pin("3", "IN", "input", (-7.62, 0))
sym.add_pin("4", "OUT", "output", (7.62, 0))
# Save the library
lib.save()
# Load and edit existing library
lib = SymbolLibrary.load("existing.kicad_sym")
Run design rule checks without requiring kicad-cli:
# Check against manufacturer rules
kct check board.kicad_pcb --mfr jlcpcb --format json
# Check against a 4-layer, 2 oz manufacturer rule set
kct check board.kicad_pcb --mfr jlcpcb --layers 4 --copper 2
Python API:
from kicad_tools.schema.pcb import PCB
from kicad_tools.validate import DRCChecker
pcb = PCB.load("board.kicad_pcb")
checker = DRCChecker(pcb, manufacturer="jlcpcb")
results = checker.check_all()
print(results.summary())
for violation in results:
print(f" {violation.rule_id}: {violation.message}")
Optimize component placement using physics-based or evolutionary algorithms:
from kicad_tools.optim import PlacementOptimizer, EvolutionaryPlacementOptimizer
from kicad_tools.schema.pcb import PCB
pcb = PCB.load("board.kicad_pcb")
# Physics-based optimization (force-directed)
optimizer = PlacementOptimizer.from_pcb(pcb)
optimizer.run(iterations=1000, dt=0.01)
# Get optimized placements
for comp in optimizer.components:
print(f"{comp.ref}: ({comp.x:.2f}, {comp.y:.2f}) @ {comp.rotation:.1f}°")
# Evolutionary optimization (genetic algorithm)
evo = EvolutionaryPlacementOptimizer.from_pcb(pcb)
best = evo.optimize(generations=100, population_size=50)
# Hybrid: evolutionary global search + physics refinement
physics_opt = evo.optimize_hybrid(generations=50)
physics_opt.write_to_pcb(pcb)
pcb.save("optimized.kicad_pcb")
CLI usage:
kct placement optimize board.kicad_pcb --iterations 1000
Optimize routed traces for shorter paths and fewer vias:
from kicad_tools.router import TraceOptimizer
from kicad_tools.schema.pcb import PCB
pcb = PCB.load("board.kicad_pcb")
optimizer = TraceOptimizer(pcb)
optimizer.optimize()
pcb.save("optimized.kicad_pcb")
CLI usage:
kct optimize-traces board.kicad_pcb -o optimized.kicad_pcb
Search, download, and parse component datasheets:
# Search for datasheets
kct datasheet search STM32F103C8T6
# Download a datasheet
kct datasheet download STM32F103C8T6 -o datasheets/
# Convert PDF to markdown
kct datasheet convert datasheet.pdf -o datasheet.md
# Extract images and tables
kct datasheet extract-images datasheet.pdf -o images/
kct datasheet extract-tables datasheet.pdf
Python API:
from kicad_tools.datasheet import DatasheetManager, DatasheetParser
# Search and download
manager = DatasheetManager()
results = manager.search("STM32F103C8T6")
datasheet = manager.download(results[0])
# Parse PDF
parser = DatasheetParser("STM32F103.pdf")
markdown = parser.to_markdown()
# Extract images and tables
images = parser.extract_images()
tables = parser.extract_tables()
for table in tables:
print(table.to_markdown())
Look up LCSC part numbers, check BOM availability, and pull pricing/stock:
from kicad_tools.parts import LCSCClient
client = LCSCClient()
part = client.lookup("C2040")
if part:
print(f"{part.mfr_part}: {part.stock} in stock, best ${part.best_price:.4f}")
Part lookups resolve through a tiered chain (each tier falls back to the next):
parts extra: pip install "kicad-tools[parts]"). Note: as of
July 2026 JLCPCB's public endpoint returns 404 — treat this tier as
best-effort/deprecated and rely on tier 2 (BYO key) or tier 4 (offline).kct parts sync-catalog, ~620 MB download, ~5 GB on disk, ~7.1 M
components), usable fully offline.kicad-tools can talk to the official JLCPCB open-platform API using credentials you register yourself at the JLCPCB developer portal (https://jlcpcb.com/ → developer/open platform). This is strictly opt-in: you bring your own key, kicad-tools ships only the signed client. Without keys, behavior is unchanged — the official tier is simply skipped.
Set all three environment variables (kicad-tools reads them via os.environ;
it does not load a .env file itself, so use your shell, direnv, or a
dotenv runner — see .env.example):
export JLCPCB_APP_ID="your-app-id"
export JLCPCB_ACCESS_KEY="your-access-key"
export JLCPCB_SECRET_KEY="your-secret-key"
All three must be set (and non-empty) to activate the official tier; if any is
missing it stays inert. Once set, LCSCClient.lookup() / lookup_many() prefer
the official API for part-detail-by-code, falling back down the chain above on
any failure.
Notes and caveats:
LCSCClient.search() always uses the anonymous/offline path even with keys..env is gitignored; the secret key is used
only as HMAC key material and is never transmitted.parts/jlcpcb_api.py (see that module and issue #4118).kct or kicad-tools)The commands below are the ones most workflows reach for. The authoritative full list (every command and subcommand) lives in CLI Reference → Commands Overview.
Inspection and validation
| Command | Description |
|---|---|
kct symbols <schematic> | List symbols with filtering |
kct nets <schematic> | Trace and analyze nets |
kct bom <schematic> | Generate bill of materials |
kct erc <schematic> | Run electrical rules check |
kct drc <pcb> | Run design rules check (requires kicad-cli) |
kct check <pcb> | Pure Python DRC (no kicad-cli needed) |
kct creepage <pcb> | HV surface-path (creepage) audit vs IEC 60664-1 / 62368-1 |
kct creepage-export-rules <project> | Export voltage-domain netclasses + pairwise HV clearance rules so kicad-cli DRC enforces creepage |
kct analyze <pcb> | Signal-integrity, current-sense, electrical-rating, and operating-state component-stress lint |
kct audit <project> | Manufacturing readiness audit (ERC, DRC, connectivity, compatibility) |
kct impedance <subcommand> | Transmission line impedance calculations |
Layout and routing
| Command | Description |
|---|---|
kct route <pcb> | Autoroute a PCB (--nets/--skip-nets to select nets, --complete to finish one) |
kct route-auto <pcb> | Orchestrator-based multi-strategy autorouting |
kct optimize-traces <pcb> | Optimize routed traces |
kct placement <pcb> | Detect and optimize component placement |
kct optimize-placement <pcb> | CMA-ES/Bayesian (--strategy bayesian, needs kicad-tools[bayesian]) global placement optimization |
kct zones <subcommand> | Add copper pour zones (and hv-keepout plane voids) |
kct stitch <pcb> | Auto-add stitching vias for plane connections |
kct reason <pcb> | LLM-driven PCB layout reasoning |
Repair
| Command | Description |
|---|---|
kct fix-drc <pcb> | Automated DRC violation repair (clearance + drill) |
kct fix-erc <schematic> | Automated ERC violation repair (PWR_FLAG + no-connect) |
kct fix-vias <pcb> | Fix vias to meet manufacturer specifications (incl. off-pad relocation) |
kct pipeline <input> | End-to-end repair pipeline for existing PCBs |
Parts and manufacturing
| Command | Description |
|---|---|
kct datasheet <subcommand> | Search, download, parse datasheets |
kct parts <subcommand> | LCSC/JLCPCB part lookup, cache, offline catalog sync |
kct export <pcb> | Manufacturing bundle export (gerbers, BOM, CPL) |
kct export-formats <pcb> | PDF / SVG / STEP / IPC-2581 / ODB++ via kicad-cli, with a per-format result |
kct mfr <subcommand> | Manufacturer rule profiles (apply-rules, validate) |
kct spec <subcommand> | Project specification (.kct) management |
kct fleet status | Routing + manufacturing readiness across all boards |
Environment
| Command | Description |
|---|---|
kct doctor | Diagnose kicad-tools installation health (version-record drift) |
kct build-native | Build the C++ router backend for 10-100x faster routing |
kct mcp serve | Start MCP server for AI agent integration |
Every kct subcommand that produces a result accepts --format json for
machine-readable output (some older commands also keep --json as a legacy
alias). A few commands are deliberately exempt -- e.g. interactive,
mcp serve and run -- see
Machine Output for the inventory,
exemptions and per-command JSON shapes.
| Command | Description |
|---|---|
kicad-pcb-query summary | Board overview |
kicad-pcb-query footprints | List footprints |
kicad-pcb-query nets | List all nets |
kicad-pcb-query traces | Trace statistics |
kicad-pcb-modify move | Move component |
kicad-pcb-modify rotate | Rotate component |
| Command | Description |
|---|---|
kicad-lib-symbols | List symbols in library |
| Module | Description |
|---|---|
core | S-expression parsing and file I/O |
schema | Data models (Schematic, PCB, Symbol, Wire, Label) |
schematic.blocks | Reusable circuit blocks (MCU, LDO, USB, debug headers, etc.) |
project | Unified Project class for schematic+PCB workflows |
library | Footprint generation and symbol library management |
drc | Design Rule Check report parsing (kicad-cli output) |
validate | Pure Python DRC checker (no kicad-cli needed) |
erc | Electrical Rule Check report parsing |
manufacturers | PCB fab profiles (JLCPCB, OSHPark, PCBWay, Seeed) |
operations | Schematic operations (net tracing, symbol replacement) |
router | A* PCB autorouter with trace optimization |
optim | Placement optimization (physics-based, evolutionary) |
reasoning | LLM-driven PCB layout with chain-of-thought reasoning |
progress | Progress callbacks for long-running operations |
datasheet | Datasheet search, download, and PDF parsing |
mcp | MCP server for AI agent integration |
pcb.layout | Layout preservation for PCB regeneration |
Release notes live in CHANGELOG.md (one section per release,
newest first) and on the
GitHub Releases page.
Changes merged since the last release are collected as fragments under
changelog.d/ until the next release assembles them.
kicad-cliThis project uses uv for fast, reproducible Python environment management.
# Clone repository
git clone https://github.com/rjwalters/kicad-tools.git
cd kicad-tools
# Set up development environment (installs all dev dependencies)
uv sync --extra dev
# Build the C++ router backend (REQUIRED for production routing speed,
# delivers 10-100x A* speedup vs pure-Python; takes ~30s on first build,
# cached thereafter). uv sync does NOT build this — fresh checkouts and
# fresh worktrees need this step explicitly.
uv run kct build-native
# Verify the C++ backend is installed
uv run kct build-native --check
# Expected: "C++ backend: available (version 1.0.0)"
# Run tests
uv run pytest
# Run linter
uv run ruff check .
# Format code
uv run ruff format .
Routing performance note: Without the C++ backend, board routing falls back to pure-Python A* — typically 5-10x slower per net, which can push medium boards (e.g. board 07 in
boards/07-matchgroup-test/) past the default 6-minute CI cap. Ifkct routeappears stuck or per-net log lines take tens of seconds, runkct build-native --checkfirst — a missing extension is the most common cause.
Pinned renders and
kct pcb model-audit --env pinnedalso need the checksum-verified KiCad 3D model subset: runuv run kct models sync --sparseonce (about 14 MB; cached under~/.cache/kicad-tools/kicad-3dmodels/). Without it they reportpinned model set not synced; nothing else breaks.
Sync the lock-pinned dev environment first:
uv sync --frozen --extra dev
A fresh worktree performs no Python env setup, and a stale or drifted
.venv/ (e.g. a mypy newer than the uv.lock pin) produces spurious
mypy-baseline noise that looks like real type regressions (issue #4558).
--frozen guarantees the resolved set matches uv.lock exactly — the
same environment CI uses. Loom-created worktrees run this automatically
via the .loom/hooks/post-worktree.sh hook; run it manually for
hand-made worktrees or if the hook reported a failure.
Build the native extension. The worktree's .venv/ does not inherit
the C++ extension built in the main checkout, and uv sync does not
build it. After cd into the worktree, run uv run kct build-native
once before any routing benchmarks.
If a local mypy error names a file outside your diff, rm -rf .mypy_cache and re-run before investigating it. .mypy_cache/ is
gitignored, so it survives git reset --hard, git clean -fd, a rebase,
and a worktree reuse — bare mypy / pnpm typecheck can replay an error
computed against an older tree. CI is always cold (no actions/cache), so
it will not reproduce the phantom. scripts/ci/check_mypy_baseline.py is
already immune (it runs --no-incremental); see
docs/contributing/development.md.
Know where kicad-cli's library tables come from before trusting native
ERC/DRC results. kicad-cli resolves stock nicknames (Resistor_SMD:)
through the global fp-lib-table/sym-lib-table in its config home,
which the GUI writes on first launch -- or never, on a fresh host, CI
container or docker run --user 0:0. Without them every stock footprint
becomes a lib_footprint_issues DRC warning and every stock symbol an ERC
warning that reads like a real defect (issues #5860, #6962; board 03 once
gained 37 of them). The library configuration is part of the pinned
toolchain, not of the host:
Where it comes from. src/kicad_tools/data/kicad-config/10.0/ holds
flat copies of the stock tables shipped in CI's pinned image (CI's
digest in ).
The build's nanobind dependency is composed into the default dev
dependency-group, so a plain uv sync keeps it resolved and a later
uv sync (e.g. adding --extra placement) will not prune it. If you
installed with only a subset of groups, add the native extra explicitly
so nanobind stays lockfile-tracked:
uv sync --extra native # repo/dev worktree
pip install "kicad-tools[native]" # consumer / installed wheel
Avoid a bare pip install nanobind — it is not recorded in the resolved
set and a subsequent uv sync will uninstall it, breaking the next
kct build-native (issue #4412).
Opening a PR from a fork? Fork PRs run on GitHub-hosted runners rather than
this project's self-hosted one, which makes the Test job roughly 2.2x slower
(~50 min, with a 90-minute budget). Nothing is skipped and nothing extra is
required of you — see
docs/contributing/development.md
for the details and the maintainer fallback if a run still cannot finish.
If you have pnpm installed, you can use these convenience scripts:
| Command | Description |
|---|---|
pnpm setup | Set up dev environment (uv sync --extra dev) |
pnpm test | Run tests |
pnpm test:cov | Run tests with coverage |
pnpm test:benchmark | Run performance benchmarks |
pnpm lint | Check code with ruff |
pnpm lint:fix | Auto-fix lint issues |
pnpm format | Format code with ruff |
pnpm format:check | Check formatting |
pnpm typecheck | Run mypy type checking |
pnpm check:ci | Run full CI suite (format + lint + tests) |
# Run tests with coverage
uv run pytest --cov=kicad_tools --cov-report=term-missing
# Run benchmarks
uv run pytest tests/test_benchmarks.py --benchmark-only
# Type checking
uv run mypy src/
# Full CI check
uv run ruff format . --check && uv run ruff check . && uv run pytest
kct namespace)kicad-tools ships its own Claude agent skills under .claude/commands/kct/ (invoked as /kct:<name>), a harness-agnostic namespace kept separate from any installed orchestration framework. Seven skills ship today — /kct:tapeout (complete fab-ready export bundle or loud refusal), /kct:manufacturing-readiness (sign-off gates), /kct:hv-isolation-loop (mains/HV creepage design loop), /kct:board-recipe-scaffold, /kct:layout-journal, /kct:ee-review (advisory EE decision document for analog/placement-blocked boards), and /kct:help (introspective guide to the namespace). See .claude/commands/kct/README.md for the full contracts. The skill text is harness-neutral; docs/agent-surfaces.md lists what each skill needs from Claude Code, Codex CLI or opencode.
The skills ship inside the kicad-tools wheel (kicad_tools/agent_skills/kct/), so a plain pip install kicad-tools is enough; no clone of this repository is needed. Install them with kct skills install:
kct skills install # ./.claude/commands/kct/*.md (Claude Code, project)
kct skills install --user # ~/.claude/commands/kct/
kct skills install --target /tmp/x # /tmp/x/kct/*.md
kct skills install --harness codex # ./.agents/skills/kct-<name>/SKILL.md
kct skills install --harness opencode # ./.opencode/commands/kct/*.md, run as /kct/<name>
kct skills install --list # packaged skills and their install state
kct skills install --check # exit 1 if the installed copy has drifted
Re-running is idempotent. --prune removes kct skill files the package no longer ships, and --dry-run previews. Edit skills in src/kicad_tools/agent_skills/kct/; .claude/commands/kct/ is a synced copy (uv run python scripts/sync_agent_skills.py --write).
Each harness needs two setup commands: one registers the MCP server, the other
installs the kct skills in that harness's own format. The skill sources are
shared, so every harness gets the same skills.
| Harness | MCP server | Skills | Invoke a skill |
|---|---|---|---|
| Claude Code | kct mcp setup --client claude-code | kct skills install (.claude/commands/kct/) | /kct:<name>, e.g. /kct:tapeout <board-path> |
| Codex CLI | kct mcp setup --client codex | kct skills install --harness codex (.agents/skills/kct-<name>/) | Codex skills: $kct-<name>, e.g. $kct-tapeout <board-path> |
| opencode | kct mcp setup --client opencode | kct skills install --harness opencode once opencode rendering lands (#5951) | /kct/<name> |
Add --user to kct skills install to install for every project. Then start
the agent with the primer:
kct agent-guide # workflow, JSON contract, sign-off rule, pitfalls
kct agent-guide --harness codex # with skill examples in Codex syntax
kct agent-guide --format json # {"version", "harness", "sections": [...]}
kct agent-guide ships in the wheel and reads the same in every harness. Point
your agent at it from CLAUDE.md or AGENTS.md instead of copying it.
kicad-tools is one piece of a fast-moving KiCad automation ecosystem. This
section is generated from src/kicad_tools/ecosystem/data/projects.toml --
run kct ecosystem where-we-sit for our invariants and non-goals, kct ecosystem show <id> for any entry below, and see docs/ecosystem.md
for the full positioning narrative. Where we have evaluated a project in
depth, the linked note carries the method, the measurements and the verdict,
including the cases where the other tool wins.
Licenses are the SPDX id from the upstream LICENSE file, and are
load-bearing: an AGPL or unlicensed neighbour is studied at the capability
level only, never copied from. Facts below were last verified on the date each
registry entry records; .github/workflows/ecosystem-drift.yml re-checks them
weekly.
kct route: octilinear multi-layer A* with via insertion, diff pairs, length matching, BGA/QFN fanout and plane pours, shipped as both a KiCad 9/10 action plugin and a CLI. Benchmarked head-to-head against us on eight demo boards under one shared kicad-cli referee: it is faster and completes more nets on the sparse and medium boards, while on the dense boards it emits literal shorts where we instead run out of time. Our evaluation: docs/research/kicad-routing-tools-comparison.md..kicad_pcb directly and is judged by kicad-cli DRC. Octilinear-lattice A* with exact legality checks, negotiated rip-up, an 8-variant deterministic portfolio under a --work budget, min-cost-flow escape-corridor reservation, a nogood cache, coupled diff-pair search, and a SQLite bandit that learns which variant to pick across runs; the GPU only accelerates cost fields. On PCBWorld D3 the circuit-skills author reports 0.99 / 1.00 / 0.90 clean on the small / medium / large splits, against about 0.7 / 0.7 / 0.2 for Freerouting 2.2.4 -- third-party, single 60 s runs, not reproduced by us, and on PCBench boards TraceMaker was developed against (its own held-out figure is 73-78% clean). Its "clean" also excludes DRC errors already in the input board, looser than our 0-DRC bar. Days old; copyleft, so ideas only, and run only as an external binary. Our evaluations: docs/research/kct-route-escape-flow-experiment.md, benchmarks/external/fleet_routers/verdict.md..ato), with constraint solving, a package registry and parametric part selection via its Faebryk library; it emits a netlist and updates a KiCad layout, but deliberately stops at "place and route in KiCad". That is exactly where we start, which makes the two complementary rather than competing. Our MCP server exists because of this evaluation. Our evaluations: docs/research/atopile-research-synthesis.md, docs/research/faebryk-component-library.md, docs/research/atopile-constraint-solving.md, docs/research/atopile-interface-patterns.md, docs/research/atopile-layout-reuse.md, docs/research/atopile-lsp-analysis.md, docs/research/atopile-mcp-analysis.md, docs/research/atopile-package-registry.md, docs/research/atopile-error-handling.md..kicad_sch / .kicad_pcb, gated by an LLM-free and wrapped in a git snapshot that auto-rolls-back on failure. It does not route, and verifies with ERC/DRC only -- no LVS, no manufacturing cross-gate. Its design (design memory as committed artifacts, a constraints registry, a per-run audit trail) is the part worth borrowing. Note: moved from since our note was written. Our evaluation: .kct export on output variety and configuration; it assumes a board that is already routed and signed off, which is the part we automate.kicad-python on PyPI) for the KiCad IPC API. Requires a running KiCad instance with the IPC API enabled and a document open in an editor -- every accessor is an RPC to the live editor; there is no offline file-reading path, so it cannot participate in headless/CI workflows. Our evaluation: docs/research/kipy-ipc-api-evaluation.md.LICENSE committed) -- Ideas adopted. A published routing benchmark and leaderboard. We adopted its metric vocabulary and evaluation protocol -- the sharpest external definition of "routed well" we have found -- and declined the dataset and tooling: no license grant on the data, and the evaluation harness is unpublished. Our evaluation: docs/research/omnilayout-recon.md.modules.easyeda.com/3dmodel/{uuid} converted to WRL; STEP passed through from the same bucket). Our evaluation: docs/research/3d-model-coverage.md.Recorded in the registry with a re-evaluation trigger, deliberately not featured above yet:
--parity mode it claims is byte-identical to Freerouting on its 20 benchmark boards. Its own measurements show about 4-5x less wall time (2102 s vs 510 s over those boards, 420 s with PGO) in 20-300 MB instead of 1-2 GB, and it adds fixes for several Freerouting optimizer bugs, multi-start, length tuning, diff pairs and impedance-derived widths. A pcbnew plugin patches the gaps in KiCad's lossy Specctra export (DRU clearances, keepouts, planes). Like freerouting it routes a translated view rather than the board file. Copyleft, so ideas only. Our evaluation: benchmarks/external/fleet_routers/verdict.md.@circuit functions over KiCad library symbols and footprints) compiled to KiCad projects, built around Claude Code with slash commands for symbol search and design help. Another upstream producer of the files we consume, in the SKiDL mould; no pushes since March 2026, and we have not run its output through our gates.LICENSE committed, Python, 0★) -- a Codex-assisted RP2354A Art-Net LED node whose KiCad 9 files come from hand-rolled Python S-expression generators, plus a SKiDL attempt. The 4-layer board is placed but carries no traces, and fails kct check --mfr jlcpcb with 255 errors, mostly stitching vias dropped onto pads -- a worked example of how hand-rolled generators drift. Not a benchmark candidate: no license and no reference copper.MIT
KICAD_IMAGE.github/workflows/ci.ymlHow producers isolate it. Every evidence producer (kct readiness,
scripts/boards/refresh_readiness.py, the scripts/boards/regenerate_*
scripts) and every KiCad CI job runs kicad-cli with a scratch
KICAD_CONFIG_HOME holding exactly those tables, and never reads or
writes your GUI config. kct's Docker fallback (KCT_KICAD_DOCKER=1)
runs the same pinned digest, not the floating kicad/kicad:10.0 tag.
Native evidence needs the pinned toolchain. Producers refuse (exit 2,
nothing written) a kicad-cli other than the image's release (10.0.6), and
refuse any DRC report with lib_footprint_issues. On a host with another
KiCad release, route kicad-cli through the image:
KCT_KICAD_DOCKER=1 uv run kct readiness boards/<board> --verify --checked-at <ISO>.
A fixed --checked-at makes two runs byte-identical.
How to check it.
uv run python -m kicad_tools.cli.kicad_toolchain --check # host kicad-cli
KCT_KICAD_DOCKER=1 uv run python -m kicad_tools.cli.kicad_toolchain --check
For ad-hoc kct check runs outside a producer, point the current shell
at the pinned tables (CI's container jobs do this in their "Initialize
stock KiCad libraries" step):
eval "$(python3 scripts/ci/init_kicad_libraries.py --shell)"
.kicad_pcbbenchmarks/external/boards.toml for side-by-side comparison. Our evaluations: benchmarks/external/README.md, benchmarks/external/results/report.md.@tscircuit/capacity-autorouter): a pipeline of hypergraph / successive-approximation solvers rather than net-by-net A*, fed a SimpleRouteJson problem instead of a KiCad file. It ships a public benchmark (dataset-srj18, derived from real Arduino and Antmicro KiCad boards) and a bug-report-to-fixture workflow. Partly benchmarked against us (Arduino Nano only): SRJ drops netclass width, clearance, zones and keepouts, so its completion is not comparable. Our evaluation: docs/research/tscircuit-evaluation.md.circuit-json-to-kicad converter emits .kicad_sch, .kicad_pcb (with traces, vias and pours) and .kicad_pro, and kicad-to-circuit-json reads them back, so it is an upstream producer of the files we consume, like atopile and SKiDL. Gated and not yet usable as a front end: five representative designs were run through kct check --mfr, LVS and kicad-cli pcb drc --refill-zones, before and after re-routing with kct route, and all five failed. The blocking defect is that its schematic and PCB converters name the same net differently, so no emitted project can pass LVS at all; vias, netclass and silkscreen defaults also sit below every mainstream fab floor. Our parser reads the files without complaint, and kct route produced opens where tscircuit's own autorouter produced shorts. Six upstream defects were recorded with reproducers and re-verified on upstream HEAD (2026-10-18): three confirmed and tracked upstream, two fixed or covered upstream, one not a bug. The relation stays upstream/complementary. Our evaluation: docs/research/tscircuit-evaluation.md.kct ingest-zip CLI is the smaller change. Not built yet. Our evaluation: docs/research/component-importer-for-kicad-evaluation.md.checkkicad-clichouhanindustries/copperheadLICENSE committed, Python, 135★) -- the closest peer to kct mcp serve: an MCP server that reads schematic and PCB files offline (shelling out to kicad-cli for validation) to trace pin-level connections and edit designs. Overlaps our read/analyze tools; carries no router, LVS or fab-readiness gates. Its README claims MIT but the repo commits no LICENSE file, so we treat it as all-rights-reserved and copy nothing.LICENSE committed, Python, 9★) -- Claude Code skills for code-driven electronics: ngspice/Falstad circuit simulation, a tscircuit-placement -> KiCad IPC (kipy) route-and-DRC loop, board/enclosure fit co-design and 3D renders. Its route_eval harness now runs five routing backends on any KiCad board (two Freerouting versions, TraceMaker, the tscircuit router, and an experimental srj-legal that has TraceMaker re-route what KiCad's DRC rejects in tscircuit output) and keeps the best, with an optional fastroute backend and a PCBWorld D3 harness; it self-reports 0.98 Clean Pass on D3-A for srj-legal, unreproduced by us. Overlaps our /kct:* skills, but still assumes the tail is hand-finished in KiCad -- the opposite of our fully automated bar. No LICENSE committed, so ideas only; mining tracked in issue #5886. Our evaluations: docs/research/circuit-skills-evaluation.md, docs/research/placement_score_calibration.md.kicad-cli, the canonical files and the KiStack skills. Its board diff is the idea we lacked for reviewing routing PRs (#6417). Note: moved from i2cjak/Backplane since our note was written. Our evaluation: docs/research/backplane-kistack-evaluation.md./kct:* skills, but its layout skill tells agents to route by hand and avoid autorouters -- the opposite of our fully automated bar. Our evaluation: docs/research/backplane-kistack-evaluation.md.kct mcp serve (pinned 628d320f): a wide imperative editing surface against our narrow session/verification one, so complementary; parts alternatives, symbol library writes and schematic cosmetic lint are worth borrowing under MIT with attribution. Its Freerouting runner is not: we improve our own router rather than add a fallback (#7991). Our evaluation: docs/research/kicad-mcp-server-evaluation.md.LICENSE committed, Rust, 0★) -- an Agent Portal plugin: a single Rust binary that wraps a user's own KiCad install with a browser workbench and agent skills. Its kct/ crate is a ~137k-line Rust port of kicad-tools taken from our commit 37665de5, keeping our command names, flags and JSON keys, and it claims parity on several command families. It adds a 101-rule lint engine whose reviewed exceptions go stale when their evidence changes, and porting surfaced four bugs in our code (#5937-#5940). Useful as a second implementation to diff our output against./kct:ee-review does not yet do; it does not route or gate manufacturing.build profile for controlled edits, and a release profile for human-gated manufacturing export. Those stages map onto our sign-off gates. It is candid that its SI/PI/EMC/thermal tools are first-order estimates rather than sign-off.kh) that gives an agent library lookup, region renders to PNG, DRC/ERC, IPC editing of an open board and datasheet-to-footprint generation; its distinctive idea is a repo the agent is expected to edit and push while using it. AGPL: ideas only, read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md.kct, routed through an external router. Read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md.auto_place_decoupling_cap helper. File-based like us. GPL: ideas only, read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md.eda-copilot-router with a managed KiCadRoutingTools backend, simple pours, and integrated kicad-happy analysis. Its router is KRT, which we have already benchmarked, so there is no separate router to measure. Read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md.kicad-python IPC API, including export jobs, design-rule access and netlist import. Live-GUI session by design, the opposite of our offline stance. Apache-2.0: ideas only. Read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md./src semantic rule-check skill. Sourcing and schematic review, not layout, so it complements kct rather than competing. Read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md./kct:* skills; content is design-rule guidance. Read from README only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md.LICENSE committed, Python, 6★) -- a third-party test bench that runs AI KiCad tools (copperhead, kicad-happy, kicad-harness, KiCad AI Assistant, kicad-mcp-pro, kicad-mcp-server, Konnect, skidl-skills) on real design prompts and records transcripts, cost and an executive summary per run. kicad-tools is not among the tools tested; the README invites outside evaluations as issues. No LICENSE: all rights reserved, ideas only. Our evaluation: docs/research/reporecon-kicad-ai-sweep.md.kct panel and kct export; we have not compared feature or fab-preset coverage./kct:tapeout BOM checks do headlessly./kct:tapeout, if its terms and refresh cadence suit us.kct route.LICENSE committed, Python, 49★) -- an ICLR 2026 benchmark of LLM knowledge about PCB placement and routing: about 3,700 text questions, about 500 image-and-text questions, and 174 OSHWHub designs to describe from screenshots. It measures what a model knows about layout, not what a tool produces, so it bears on our agent-facing docs rather than on kct route. No LICENSE, so ideas only.LICENSE committed, Python (FreeCAD), 696★) -- FreeCAD workbench for aligning 3D models to footprints, the manual analogue of our transform calibration. The README claims AGPLv3 but no LICENSE file is committed (hence license = "NONE"); ideas only either way. Our evaluation: docs/research/3d-model-coverage.md.