main
Colby Knox standard 0.29.3: coverage reads KiCad 1.0.93; sheetDrawing sizeMm 4ee6340 11d ago

Conventions every bridge follows

These started as the rules the reference bridge (Altium) keeps, and grow with what every bridge learns. A bridge that keeps them lets one AI drive every EDA tool the same way, and lets the shared checks run on any of them.

Names

  • A verb is <prefix>_<name>: altium_route_net, kicad_route_net, fusion_route_net. The <name> comes from verbs/standard.json. A tool that does the same job under its own verb keeps it and adds the standard name as an alias, so docs/coverage.md can list it as exact.
  • A verb a tool cannot do at all is not faked. Leave it out, or answer success:false with errorCode: "not_supported" and an error that says why.
  • Coverage counts only what works. A verb that answers not_supported for its whole job is marked unsupported in the bridge's tools/equivalents/<bridge>.json with the reason (["sch_update_from_library", "unsupported", "the tool's library manager asks about each part"]): listed, not counted. A verb that refuses some of its arguments is partial, with a note naming them. The entry wins over a name match, so this works for a verb under its standard name too.
  • Tool-only verbs (Altium 365, OutJobs) keep their own names under the platform tier and stay out of the standard.
  • A job the standard lacks is proposed in tools/proposed/<thread>.json and joins once another bridge's thread agrees (README, Proposing a verb). Board verbs have no prefix and call a placed part a part; sheet verbs start with sch_; *_symbol and *_footprint act on library files.

Units, layers, frames

  • Millimetres everywhere in replies and arguments; degrees for angles, counterclockwise from +x. An argument in other units says so in its name (copperUm, wMil).
  • Coordinates are absolute in the standard frame: the tool's origin and x axis, y up (a y-down tool negates y and nothing else). What routing_state reports is what route_net takes. See board-model.md.
  • A reply field that carries its unit in its name says mm: xMm, widthMm, areaMm2. Some of the reference bridge's older verbs still answer in mils (xMil, marked unitsNote in verbs/standard.json); that is the reference's debt, not the standard. A new bridge answers those verbs in mm under Mm names.
  • Schematic sheets follow the same rule: millimetres, the tool's sheet origin, y up. A pin is named Ref.Pin (U1.3), and an item (a wire, a label, a junction) by the tool's own stable id, as sch_items lists it.
  • sheet is the sheet's file path. On a tool whose sheets are not files (Fusion keeps them in one design), sheet is the sheet's number, from 1, and filePath names the design. A tool that stores no item ids builds stable ones (Fusion: the item's kind and position) and says so in sch_items' hint.
  • save defaults to true on the sheet verbs, except where a save has a cost the user would notice (Fusion: every save is a new cloud version): there it defaults to false, and the verb's describe says so.
  • Copper layers carry KiCad's names on every tool: F.Cu, In1.Cu to In30.Cu, B.Cu, and *.Cu for a pad on every copper layer. A layer argument also takes the tool's own id.
  • Nets are named as the design names them; matching is case-insensitive.

Replies

Every reply is one JSON object:

Key Meaning
success true when the verb did what it says. A check that ran and found problems is success: true with its findings; ok (or fits, ready) carries the verdict
errorCode on success:false: a short stable code from the table below
error on success:false: one sentence a person can read, naming the thing (the net, the pad, the file)
_hint what to do next, in words. The AI reads replies, not skill files: the hint is where the next step is taught
related / _next verbs that usually follow
revision after an edit: the board's new revision (pass it back as expectedRevision)
bridgeVersion the bridge's version
windowEtiquette background when the verb took no focus from the user

Verbs that change a design:

  • Take expectedRevision and refuse with stale_board when the board changed since the caller read it.
  • Land as one undo step in the tool.
  • Read back what they wrote and report it: positions to 0.001 mm, rule descriptors from the tool, poured area.
  • Plan before acting when the plan can be wrong: dryRun: true answers the plan (points, sizes, what it replaces) and changes nothing. Planners that can short something check the plan first and refuse with the problem and the planned geometry (pair_plan_blocked), never place it.
  • Never save over a file without being told (overwrite: true), and never answer a save prompt for the user.

Checks:

  • Name each finding with where it is (at: [x, y], a designator, a pad U1.3, a net).
  • Separate errors from warnings, and say when a check could not see something (noBody, poursWithoutRegions, complete: false) rather than passing it.
  • Read a tool's numeric constants from the tool, never assume them. A severity (error or warning), a state (approved, waived) or a kind that the tool hands over as a number means what the tool's own API defines, and that differs from tool to tool: Fusion's ULP has ERROR_TYPE_WARNING = 1, ERROR_TYPE_ERROR = 2 and ERROR_STATE_APPROVED = 2, and the Fusion bridge reported DRC errors as warnings and warnings as errors until it read them. Answer the standard's words (severity: "error"), not the tool's numbers.
  • Name the source of any outside number (a fab's limit: its page and the date it was read). Fab limits come from adom/pcb-design-rules, the canonical record per vendor.

Refusal codes

The common ones. A bridge adds its own for its tool's states, in the same snake_case form.

Code Meaning
bad_args a required argument is missing or out of range: error says which
not_supported this tool cannot do this verb
app_not_running / <tool>_not_running start the tool first (launch)
not_licensed the tool runs without a licence that allows scripting
no_current_board / no_board no board document is open: open one or pass filePath
wrong_board / board_mismatch the open board is not the one named
stale_board expectedRevision no longer matches: read the board again
no_such_net / no_such_pad / part_not_found / designator_not_found the named thing is not on the board
no_outline the board has no outline
not_routed the net has no routed copper
blocked_by_dialog a dialog in the tool blocks the verb: dialogs names it
project_not_found / not_a_schdoc / unsupported_document the file is not what the verb needs
write_failed the bridge could not write a file it was asked to
nothing_to_do the request asked for no change
*_plan_blocked, no_room, outline_on_mechanical_layer a planner refusing a plan that would be wrong, with why

Driving the user's tool

  • Background first: verbs work without taking the window or the mouse. A verb that must take focus says so in its docs and gives the user's window back after.
  • A dialog the user would have to dismiss is a bridge defect: pre-empt it, answer it only when the answer carries no decision (a lone OK), keep a picture of anything answered without being read, and never answer a save prompt.
  • Never kill the tool when it can be closed: a killed EDA tool can strand a licence seat.

Every verb documents itself

<prefix>_describe returns every verb with summary, input, output, hint, pitfalls, related, example and timeoutSeconds. verbs/standard.json is generated from the reference bridge's describe and the agreed proposals in tools/proposed/.

Shared vocabularies

Some verbs answer with a fixed set of words, so an AI reads every bridge's answer the same way. A bridge can add a word by proposing it on the page's issues.

update_footprints_from_library statuses (#27)

status meaning
up_to_date the placed footprint matches the library's
differs it doesn't; diff says how (padsAdded, padsRemoved, padsChanged[{pad, changes}], bodies3d?)
library_not_found the footprint's library or the footprint in it wasn't found; why says which
unusable the library copy was found but can't be laid onto the board; why says why. Read it like library_not_found

A plan (apply false, the default) changes nothing. Tracks don't follow pads that moved.

lint_library rules (#28)

Severity belongs to the rule, not the bridge. A bridge emits only the rules it can evaluate, so a missing finding is not a pass. ok is true when there are no errors and nothing is unread; warnings don't fail it. The shared checks engine (lint_library) evaluates every rule but tool_refuses_file from the footprints and symbols a bridge reads.

kind rule severity what it means
footprint no_pads error no pads at all
footprint zero_size_pad error a pad with no copper and no hole
footprint unnumbered_pad warning a pad that isn't mechanical has no number
footprint duplicate_pad_number warning one number on lands apart from each other (pads that touch or overlap are one land)
footprint no_courtyard warning no courtyard
footprint origin_off_part warning the origin is outside the pads and courtyard, grown by 1 mm
footprint no_silkscreen info no silkscreen
footprint no_3d_body info no 3D body
footprint no_description info no description
symbol no_pins error no pins
symbol footprint_not_found error the linked footprint isn't in the libraries checked
symbol pin_without_pad error a pin number has no pad on the linked footprint
symbol unnumbered_pin warning a pin has no number
symbol duplicate_pin_number warning two pins share a number
symbol no_footprint warning no footprint is linked
symbol pad_without_pin warning a pad (not mechanical) on the linked footprint has no pin
symbol no_part_number info no manufacturer part number
library tool_refuses_file error the tool itself won't read the file; detail carries its message

sheetDrawing (the drawing companion for schematic_review, #29)

One entry per sheet, in mm with y down (a tool that keeps mils converts):

{"sheet": "/Power/", "wires": [{"a": [101.6, 76.2], "b": [127.0, 76.2]}], "buses": [{"a": [50.8, 50.8], "b": [50.8, 101.6]}],
 "junctions": [[127.0, 76.2]], "noConnects": [[152.4, 88.9]],
 "labels": [{"text": "SDA", "kind": "local | global | hierarchical | power", "at": [120.65, 63.5]}],
 "pins": [{"ref": "U1", "number": "3", "at": [101.6, 75.89]}], "symbols": [{"ref": "U1", "box": [80.0, 60.0, 101.6, 95.0]}]}

A point connects when it lies within 0.01 mm of another item. A wire end on another wire's middle connects only where there is a junction. A symbol's box is its body and pins, not its fields. An entry may carry sizeMm: [width, height], the sheet's size, so y counts down from its top edge (altium-lane). Without this companion, dangling_wire_end, floating_label and overlap are listed in unread.