app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
main
John Lauer
Publish 0.2.56
73fcf19
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
"""Issues #150 and #152 end to end, with a stand-in kicad-cli: `netlist check` runs ERC on the same
schematic and records it (finish then says ERC yes); `outline check` records the outline proof when
no corner is sharp, from any step, so no return is needed; finish names that command; a return that
changes neither board nor spec leaves the later steps done. No CAD, bridge or dashboard."""
import json, os, pathlib, stat, subprocess, sys, tempfile
binary = str(pathlib.Path(sys.argv[1]).resolve())
EDGE = " ".join([
'(gr_line (start 1 0) (end 29 0) (layer "Edge.Cuts"))', '(gr_line (start 30 1) (end 30 19) (layer "Edge.Cuts"))',
'(gr_line (start 29 20) (end 1 20) (layer "Edge.Cuts"))', '(gr_line (start 0 19) (end 0 1) (layer "Edge.Cuts"))',
'(gr_arc (start 29 0) (mid 29.7071 0.2929) (end 30 1) (layer "Edge.Cuts"))', '(gr_arc (start 30 19) (mid 29.7071 19.7071) (end 29 20) (layer "Edge.Cuts"))',
'(gr_arc (start 1 20) (mid 0.2929 19.7071) (end 0 19) (layer "Edge.Cuts"))', '(gr_arc (start 0 1) (mid 0.2929 0.2929) (end 1 0) (layer "Edge.Cuts"))'])
BOARD = """(kicad_pcb (version 20241229) (generator "test") (general (thickness 1.6))
(layers (0 "F.Cu" signal) (2 "B.Cu" signal) (25 "Edge.Cuts" user) (37 "F.SilkS" user))
(net 0 "") (net 1 "A") (net 2 "B")
EDGE
(footprint "R:R0603" (layer "F.Cu") (at 10 10) (property "Reference" "R1" (at 0 -1.5) (layer "F.SilkS"))
(pad "1" smd rect (at -0.8 0) (size 0.8 0.9) (layers "F.Cu") (net 1 "A"))
(pad "2" smd rect (at 0.8 0) (size 0.8 0.9) (layers "F.Cu") (net 2 "B")))
(footprint "R:R0603" (layer "F.Cu") (at 20 10) (property "Reference" "R2" (at 0 -1.5) (layer "F.SilkS"))
(pad "1" smd rect (at -0.8 0) (size 0.8 0.9) (layers "F.Cu") (net 1 "A"))
(pad "2" smd rect (at 0.8 0) (size 0.8 0.9) (layers "F.Cu") (net 2 "B"))))
""".replace("EDGE", EDGE)
NETLIST = """(export (version "E") (design (source "x.kicad_sch") (tool "Eeschema 10.0.6"))
(components (comp (ref "R1") (value "10k") (footprint "R:R0603")) (comp (ref "R2") (value "10k") (footprint "R:R0603")))
(nets (net (code "1") (name "/A") (node (ref "R1") (pin "1")) (node (ref "R2") (pin "1")))
(net (code "2") (name "/B") (node (ref "R1") (pin "2")) (node (ref "R2") (pin "2")))))"""
FAKE = """#!/bin/sh
out=""; prev=""
for a in "$@"; do [ "$prev" = "--output" ] && out="$a"; prev="$a"; done
here="$(dirname "$0")"
case "$1 $2" in
"sch export") cp "$here/netlist.net" "$out";;
"sch erc") cp "$here/erc.json" "$out";;
*) exit 1;;
esac
"""
with tempfile.TemporaryDirectory(prefix="aiflow-erc-outline-") as td:
p = pathlib.Path(td)
tools = p / "tools"; tools.mkdir()
(tools / "netlist.net").write_text(NETLIST)
(tools / "erc.json").write_text(json.dumps({"sheets": [{"path": "/", "violations": [{"severity": "warning", "description": "lib warning"}]}]}))
cli = tools / "kicad-cli"; cli.write_text(FAKE); cli.chmod(cli.stat().st_mode | stat.S_IEXEC)
env = dict(os.environ, AIFLOW_DASHBOARD="off", AIFLOW_NO_PART_SEARCH="1", AIFLOW_NO_PART_LOOKUP="1", ADOM_AIFLOW_KICAD_CLI=str(cli))
(p / "board.kicad_pcb").write_text(BOARD)
(p / "board.kicad_sch").write_text('(kicad_sch (version 20250114) (generator "eeschema"))\n')
(p / "spec.json").write_text("{}")
def call(*args):
return subprocess.run([binary, "--run", "run", "--ai-thread", "erc-outline-test", *args], cwd=p, env=env, capture_output=True, text=True, timeout=120)
def run_json():
return json.loads((p / "run" / "run.json").read_text())
r = call("start", "--board", "board.kicad_pcb", "--spec", "spec.json", "--engine", "test")
assert r.returncode == 0, r.stdout + r.stderr
# issue #150: finish's ERC hint names the command; netlist check runs ERC and records it
r = call("finish")
assert r.returncode == 0 and "ERC not known" in r.stdout and "netlist check` runs KiCad's ERC" in r.stdout, r.stdout + r.stderr
r = call("netlist", "check", "--sch", "board.kicad_sch")
assert r.returncode == 0 and "ERC (kicad-cli sch erc --severity-all): 0 errors" in r.stdout, r.stdout + r.stderr
erc = run_json()["outcomes"]["erc"]
assert erc["pass"] is True and erc["source"].startswith("netlist check"), erc
r = call("finish")
assert "ERC yes" in r.stdout, r.stdout
# an ERC error is recorded as a failed ERC, and the parity check still answers
(tools / "erc.json").write_text(json.dumps({"sheets": [{"path": "/", "violations": [{"severity": "error", "description": "Pin not driven"}]}]}))
r = call("netlist", "check", "--sch", "board.kicad_sch")
assert r.returncode == 0 and "1 error(s): Pin not driven" in r.stdout, r.stdout + r.stderr
assert run_json()["outcomes"]["erc"]["pass"] is False
# a kicad-cli that cannot run ERC: a note, nothing recorded, the command completes
(tools / "erc.json").unlink()
r = call("netlist", "check", "--sch", "board.kicad_sch")
assert r.returncode == 0 and "ERC did not run" in r.stderr, r.stdout + r.stderr
(tools / "erc.json").write_text(json.dumps({"sheets": []}))
assert call("netlist", "check", "--sch", "board.kicad_sch").returncode == 0
# issue #152: the outline step visited, then left without its proof
for s in ["rules", "outline", "current"]:
assert call("step", s).returncode == 0
r = call("finish")
assert "no outline recorded during its last visit; `adom-aiflow outline check` records it from any step, no return needed" in r.stderr, r.stderr
# outline check from a later step records it (no sharp corner): the step is proven without a return
r = call("outline", "check")
assert r.returncode == 0 and "recorded" in r.stdout, r.stdout + r.stderr
assert run_json()["outcomes"]["outline"]["by"] == "check"
r = call("finish")
assert "step outline" not in r.stderr, r.stderr
# a return that changes nothing: the later step stays done
assert call("step", "thermal").returncode == 0
r = call("step", "outline", "--back", "--why", "look again")
assert r.returncode == 0, r.stdout + r.stderr
assert call("outline", "check").returncode == 0
assert call("step", "thermal").returncode == 0
r = call("finish")
assert "current (redo" not in r.stderr, r.stderr
visits = run_json()["steps"]["outline"]["visits"]
assert visits[-1]["back"] is True and visits[-1]["hashes"] == visits[-1]["endHashes"], visits[-1]
# a return that changes the board: the later step is a redo again
r = call("step", "outline", "--back", "--why", "move a part")
assert r.returncode == 0
board = pathlib.Path(run_json()["boards"][-1])
board = board if board.is_absolute() else p / board
board.write_text(board.read_text().replace("(at 20 10)", "(at 21 10)"))
assert call("step", "thermal").returncode == 0
r = call("finish")
assert "current (redo" in r.stderr, r.stderr
print("erc-outline-workflow: OK")