Skip to main content

circuitcode

Use when the user wants to create, edit, or fabricate a PCB from a natural-language description — "design me a macropad", "an ESP32 soil-moisture board", "a desk air monitor", "add a second button", "make the board smaller", "fix the DRC error", "regenerate the gerbers", "how much to get 5 made" — or to tweak, re-check, or fix an existing board `.tsx` in an Autonomous Circuit board project (product.json + boards/).

설치로 이동

소스 정보

저장소
autonomous-ai/autonomous-circuit
최근 소스 활동
2026년 9월 15일 18:59
감지된 SKILL.md 언어
영어
스타
1
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
49 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
circuitcode
description
Use when the user wants to create, edit, or fabricate a PCB from a natural-language description — "design me a macropad", "an ESP32 soil-moisture board", "a desk air monitor", "add a second button", "make the board smaller", "fix the DRC error", "regenerate the gerbers", "how much to get 5 made" — or to tweak, re-check, or fix an existing board `.tsx` in an Autonomous Circuit board project (product.json + boards/).
# circuitcode — real boards from board programs ## Purpose A board is **code**: `boards/main.tsx` composes golden blocks into a device, and the pipeline compiles it to a Circuit JSON, a schematic, a PCB layout, and a fab packet a manufacturer will actually build. The source is the truth. The user owns the project; you write the program, run the gauntlet, look at the pictures, and fix what is wrong. What comes out the far end is not a design file. It is a zip a person uploads to JLCPCB, plus a walkthrough, plus a 3D body for the printed enclosure — five assembled boards for about $75–110, in their hands in one to two weeks. The reason this can work at all is the block library. **You compose validated subcircuits; you never invent a circuit from a datasheet.** That is not a style preference — see *Non-negotiables*. ## Make it buildable and repairable — by default Four habits, applied without being asked: 1. **Compose, don't invent.** Every IC comes in through a block from `circuitlib.blocks`. Glue between blocks (a resistor, a capacitor, an LED, a header, a connector) is yours to place. A chip that is not in a block does not go on the board — say so and offer the nearest block. The one way to make the catalog longer is `block-source` (`$CIRCUIT_SKILLS_DIR/block-source`, which is `~/.claude/skills/block-source` unless the host sets it), which sources a passive interconnect, a certified module, or a non-radiating integrated module that carries every active part it needs, from the supplier with graded provenance. Run it **first thing in the build turn**, before you write a line of board source, and never inside the edit/build/read loop — it is one network step, taken once. It does not loosen this rule: a part whose circuit would be yours to draw is still refused. 2. **Decouple everything, always.** One 100nF beside every IC power pin, one bulk cap per rail. `circuitlib.helpers.decoupling_for()` tells you the count. Blocks bring their own; glue ICs do not exist, so this mostly means: don't strip a block's caps to save space. 3. **Label every net.** `net.V3_3`, `net.I2C_SDA` — never an anonymous trace. An unnamed net is a warning from the pipeline and an unreadable schematic for the human who has to debug the board. 4. **Leave the enclosure something to hold.** Four mounting holes at the corners on a pitch you state (27 of 32 boards built have them; two on one edge cantilevers the rest — `dfa_mounting_collinear`), connectors on one edge, and a board outline inside the declared envelope. Fewer holes is a choice you make on purpose and say so. This board is going inside a 3D-printed body. 5. **One side by default; the back when the face is spoken for.** If the user-facing parts (screen, LED ring, buttons, sensors) fill the front at the user's size, put the MCU core on the back as one block — `<Rp2040Core layer="bottom" pcbRotation={180} …/>` with `doubleSidedAssembly={true}` on the board and `"assemblyTier": "standard"` in `product.json` (economic PCBA places one side only and skips the rest silently — `dfa_bottom_side`; standard places both at a higher price band) — and say so in the plan with the cost. The placement ruler scores each side (`congestion.bySide`, `pinsBySide`). The shipped router has failed a dense two-sided 54 mm board before (2026-09-08); if it hands back unrouted nets, that is its limit — report it, do not spend rounds. 6. **Land the debug interface.** An MCU block brings SWCLK/SWD out as nets and terminates neither. If nothing does, the assembled board cannot be halted, single-stepped or recovered from a bad image — every part on it correct and the product useless. `board_plan().must_expose` names the nets; `DebugPort` from `blocks/glue` lands them on three 2.54mm pads. **Any other row of labelled pads is the same component under its own name** — `PadHeader`, also from `blocks/glue`, takes `nets`, `labels`, `pitch` and `padDiameter`, so an off-board strip connector or a bench breakout is one import rather than a hand-copy of `DebugPort`'s internals (which is what two engineers did on 2026-08-17 before it had a name they would look for). Put it in open board space, not inside the MCU block: three pads inside `rp2040-core`'s own box route the debug pair through the crystal cluster and the router comes back with a via shorted into the QFN pad field (measured 2026-08-11). 7. **Say what routing effort the board needs.** `autorouterEffortLevel="10x"` is the floor on every board. The same rp2040-core board is `fab.ready: false` with five blocking KiCad findings at the default effort and `fab.ready: true` with zero at `"5x"` — same design, only this prop changed. **The floor was 5x until 2026-09-08, and 5x is a false economy.** A board that fails at 5x does not simply fail: the pipeline escalates one rung and **builds the whole thing again**, so a board needing 10x pays for two full builds instead of one. Measured that day, same brief, two boards: one asked for 10x up front and finished in a single attempt; the other followed this floor at 5x, failed, escalated, and spent 35 minutes to arrive at the same 29 blocking findings twice. Starting at the top rung costs more per attempt and less per board. It also disables the escalation, which is the point — there is nowhere left to climb, so a failure is reported once, immediately, instead of being re-proved at a cost of twenty minutes. **Read the verdict, do not hand-rebuild at a higher effort to see.** If the sidecar says routing still failed at 10x, effort is not the lever any more: the placement is, and on a 2-layer board the first thing to check is whether components ended up on both sides. **The one case it still cannot reach**: findings that only KiCad can see. The escalation decides off the circuit.json scan, and `drc_violation` — clearance, shorting, hole-clearance — is produced by the KiCad cross-check several stages later, so a board whose *only* blockers are DRC ones gets no retry. That is the case where declaring `"10x"` yourself is still the move, and it is why the fleet's RP2040 boards were rebuilt by hand. **Since v1.8 (2026-09-11) the copper of record is Freerouting's, not the compiler's**, whenever the toolchain has it (`build.router.engine == "freerouting"`). The compiler still routes first (that is the incumbent); then the board goes to Freerouting at the rules' target and, if nets stay open, once more at the fab floor plus a hair; routerlib patches what is still open with Freerouting's copper as obstacles; and any net nobody closed stays open by default. Mixing compiler copper into the result is opt-in (`CIRCUIT_ROUTER_FILL_FROM_INCUMBENT=1`) because it was routed against different neighbours and can create shorts. The stage keeps the whole incumbent if connectivity would regress. `build.router.applied == false` with a `reason` is the compiler's copper, unchanged. `CIRCUIT_ROUTER=off` pins the compiler's router for an A/B. ## Treat the device as a project ``` <project>/ ├── product.json the bible: name, power, envelopeMm, layers, fab, assembly ├── parts.json the locked BOM — parts-book owns it; you never write it ├── blocks/ the golden-block library, frozen with this project ├── boards/ │ ├── main.tsx your file — the only file you write │ └── main.circuit.json + main.board.json + main_review/ + main_fab/ generated ├── tsconfig.json, tscircuit.config.json └── .circuit/ build cache — never edit, never read ``` ### Starting a new project The app creates the workspace; you fill it. From the skill's own templates: ```bash SKILL="${CIRCUIT_SKILLS_DIR:-$HOME/.claude/skills}/circuitcode" cp -R "$SKILL/templates/project_skeleton/." /abs/project/ ``` `boards/main.tsx` imports blocks by relative path, and a project that owns its own snapshot keeps building the same board after the shared library moves on. **You do not copy them yourself — the build seeds `blocks/` from the library on a project that has none**, and reports what it wrote. Then edit `product.json` (name, description, power, envelope) before you write any board source. **Never copy `blocks/` from another project.** It is the one way this has gone wrong: on 2026-08-21, weather-badge-16 through -25 — eight boards over two and a half days — were found holding byte-identical blocks inherited from each other rather than from the library, and a fix that unshorted every button on every board with one reached none of them. A build now says so, at `warning`, whenever a board that has never been built already disagrees with the library. Rules of the project format: - **Device-wide facts live in `product.json` only.** Power source, envelope, layer count, fab profile. Don't restate them in the board file; read them. - **One file per board.** Most projects have exactly one, `boards/main.tsx`. - **Every number comes from a table.** `circuitlib.tables` owns the electrical law and the fab limits. Retyping `0.127` into a board file makes a number nobody can update. - **Never edit generated artifacts.** `.circuit.json`, `.board.json`, the SVGs, the PNGs, the fab packet — all outputs. Editing one gets it overwritten on the next run and desynchronises the sidecar. - **Never edit `parts.json`.** That is `parts-book`'s file. If a part is wrong or out of stock, hand off; don't hand-patch the lock. ### The source contract `boards/main.tsx` default-exports a function returning one `<board>`: ```tsx import { UsbCPower } from "../blocks/usb-c-power/usb-c-power" import { Ldo3v3 } from "../blocks/ldo-3v3/ldo-3v3" export default () => ( <board width="40mm" height="30mm" thickness={1.6}> <UsbCPower pcbX={-14} pcbY={0} schX={-6} schY={0} /> <Ldo3v3 pcbX={0} pcbY={6} schX={0} schY={0} /> <hole name="H1" diameter="3.2mm" pcbX={-17} pcbY={-12} /> </board> ) ``` Four things that are not optional: - **`thickness={1.6}` explicitly.** The toolchain default is 1.4mm; JLC's standard stackup is 1.6mm. Leave it out and you ship the wrong board. - **Both coordinate systems on every placed thing.** `pcbX/pcbY` put it on the board; `schX/schY` put it on the schematic. Skip the schematic coordinates and you get a legible board with an unreadable drawing — and you must read that drawing later. - **A pinned-dialect header comment** naming the tscircuit version the file targets (copy the template's). The toolchain ships roughly seven releases a day; the file should say what it was written against. - **Blocks by relative import** from `../blocks/<id>/<id>`. Never an `@tsci/…` registry import — those are mutable, unreviewed, and fetched at build time. ## The loop **Two loops, in this order.** First the *placement* loop — edit TSX, run `preflight` (seconds, no router), `Read` `_placement.png`, read the `placement` score — until the parts sit where the copper can reach them; only then `scripts/circuit` to route. Then the *repair* loop — read a copper finding, write `edits.json`, run `scripts/circuit … --edits`, read the verdict in thirty seconds — until the copper is clean. Do not go back to the placement loop for a copper finding: a rebuild re-routes every net and the finding moves somewhere else. **The placement ruler** (`placement` in `preflight` and `fastcheck`, `build.placement` in the sidecar, `placement_summary` finding): `ratsnestMm` (the shortest copper that could ever connect every net), `crossings` (net lines that cross — each is a via pair or a detour you are asking the router to pay for), `congestion.worst` (pins in the fullest 5 mm cell), `decoupling` (each cap to the chip pin it serves; over 3 mm is a finding an engineer raises on sight), `crystals`, `connectorsToEdgeMm`, `longestNets`. Push these down before routing: move the part that owns the worst number, re-run preflight, look at the picture. Measured 2026-09-11: a board that iterated placement 62 times against overlaps and price tier alone routed to 190 vias and 789 jogs; its ruler read 240 crossings and 10 decoupling caps over 3 mm. A placement that scores well routes with fewer vias — that is the whole point of the order above. ``` understand ask → inspect project → block plan → edit main.tsx ↑ ↓ └── fix ← Read _schematic.png + _pcb.png ← read the JSON verdict ← run scripts/circuit ``` `ok: true` says the pipeline ran. Only your eyes say the board is right. ## Done means orderable **A board is finished when `fab.ready` is `true`, and at no other time.** Dee, 2026-08-11: *"All designs generated must be ready to be sent to JLCPCB. Perfect, no issue, board generated one shot, printed."* That is one gate, not a scale: | Sidecar says | What you say | |---|---| | `fab.ready: true` | **Done.** Here is the packet, here is what it costs, here is how to order it. | | `fab.ready: false` | **Not done.** One line on the single thing missing, then keep working or hand back a clear blocker. | `ok: true`, "zero blocking warnings", "the pictures look right" and "the build is clean" are all *inputs* to that gate. None of them is the gate. A packet with unverified gerbers (`gerberSource: "tscircuit"`, kicad-cli absent) is `fab.ready: false` and therefore unfinished, even with zero warnings — a user cannot send it to a fab, so it is not a board yet. ### `fab.ready: true` is a floor. Never trade it for a tidier verdict. **Once a build reports `fab.ready: true`, no later change may lose it.** If a fix for a non-blocking finding costs readiness, **revert the fix** and report the finding as accepted, with its measurement, in the final response. This is not hypothetical. On the 2026-08-11 agent eval, `macropad-6` was `fab.ready: true` on its **first** build and `false` after five repair rounds: the loop chased findings that were never blocking anything and gave away the board the user had already got. `usb-blinky` on the same run went ready → ready over three rounds, so it is not inevitable — but once is enough, because it is the worst failure available to this skill. The router has had the right instinct all along: stage 0b keeps an escalated route **only when it has strictly fewer blocking warnings**. Apply the same rule one level up. - After every build, compare `fab.ready` with the previous build's. - `true` → `false` is a **regression, not progress**. Undo the change that caused it before doing anything else, and say what you reverted and why. - A warning or an info finding is never worth an orderable board. "Zero findings" is not the goal; **orderable** is the goal, and the finding count is only ever evidence about it. **Aim to earn it on build #1.** Every repair round is a defect that should have been prevented before the first build: read the BLOCK.md files, use `circuitlib.layout.place_board()` for the outline, placement and mounting holes instead of guessing coordinates, and `helpers.board_plan()` for the block set. If you find yourself fixing the same class of thing twice, the real bug is upstream — say so, because that fix belongs in the block or the library, not in this board. ## Plan-phase design discipline When the app runs you in Plan mode, **write no files.** Produce an engineering spec the user can approve or redirect: 1. **What it does** — one paragraph, in the user's terms. 2. **Block plan** — the table: capability → block → why. Call `circuitlib.helpers.board_plan(capabilities=[...])`; report anything it returns in `unavailable` honestly instead of inventing a circuit for it. 3. **Power budget** — source, rail tree, the arithmetic. `V5 @ 1.5A → LDO → V3_3`; sum the block draws; state the headroom. 4. **Pin allocation** — every MCU pin you intend to use, and for what. 5. **Size and layout intent** — board outline, what sits on which edge, where the mounting holes go, and whether it fits `product.json`'s envelope. 6. **Cost band** — `circuitlib.helpers.estimate_cost()` plus the parts total from the lock. Quote the assembled-5x band, not a fantasy unit price. 8. **Safety verdict** — run `circuitlib.safety.safety_gate()` on the ask. If it refuses, the plan is the refusal and the reason. Don't design around it. ### Where the numbers come from — the tables own them | Number you need | Where it lives | |---|---| | Rail voltages and tolerances | `circuitlib.tables.RAILS` | | Trace width for a current | `helpers.trace_width_for(current_a=…)` (IPC-2221) | | Conductor spacing for a voltage | `helpers.clearance_for(volts=…)` | | Min trace/space/drill/annular/edge | `tables.MIN_*` | | Board thickness, layer default | `tables.BOARD_THICKNESS_MM`, `tables.DEFAULT_LAYERS` | | Decoupling counts | `helpers.decoupling_for(power_pins=…, rails=…)` | | I2C pull-ups, USB series/CC resistors | `tables.I2C_PULLUP_OHMS`, `tables.USB_*` | | Assembly fees, cost bands, lead time | `tables.*_USD`, `helpers.estimate_cost()`, `helpers.fab_profile()` | | Which block does X | `blocks.block_for(capability)`, `blocks.CAPABILITY_INDEX` | | A real, stocked glue part | `parts.pick("resistors", resistance=4700)` — JLC Basic/Preferred mirror | | Is this part costing us a feeder fee | `parts.cheaper_basic_part("C…")` | | Will the regulator cook | `helpers.regulator_thermal(vin=…, vout=…, current_a=…)` | | Is this LED resistor sane | `helpers.led_current(rail_v=…, resistance_ohms=…)` | | What a block needs fed | `blocks.BLOCKS[id].requires` / `.provides` | | Iteration cap | `tables.MAX_REPAIR_ITERATIONS` | | How big a block is, and where it sits | `layout.box(block_id)` -> `(min_x, min_y, max_x, max_y)` around its origin | | **The whole board plan** | `layout.place_board([...])` -> outline + placements + holes + its own warnings | | Room for something that is not a block | `layout.reserve(name, w, h)` / `layout.pad_header_extent(pads)` — pass the name in with the blocks | | Where to put a row of blocks | `layout.place_row([...])` -> `{block: (pcbX, pcbY)}` (the primitive) | | How big the board must be | `layout.min_board_for([...], columns=n)` | | Will anything hang off the edge | `layout.board_fits(placements, w, h)` — run it *before* building | If a number is not in a table and not in a block, it is an **estimate** — say so out loud rather than presenting it as measured. ## Use this skill when The user wants a board designed, changed, checked, or made orderable. **Do not use it for:**
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기