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.48
7fce739
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
"""Issue #60 through the CLI: a declared mechanical review becomes a step before finish, finish refuses
it missing, a missing tool is refused with how to get it, a failing review records nothing, a passing
review (a fake adom-aiflow-mechanical on PATH speaking the prototype's contract) satisfies finish, a
different board voids it, and a spec that declares nothing is unchanged. No CAD, desktop or dashboard."""
import json, os, pathlib, subprocess, sys, tempfile
binary = str(pathlib.Path(sys.argv[1]).resolve())
HEAD = '(kicad_pcb (version 20240108) (net 0 "") (layers (0 "F.Cu" signal) (31 "B.Cu" signal)) (gr_circle (center 10 10) (end 19 10) (layer "Edge.Cuts")) (gr_circle (center 10 10) (end 14 10) (layer "F.Cu") (width 0.5))'
def hole(x):
return f' (footprint "MountingHole:MountingHole_3.2mm_M3" (layer "F.Cu") (at {x} 10) (property "Reference" "H1") (pad "" np_thru_hole circle (at 0 0) (size 3.2 3.2) (drill 3.2) (layers "*.Cu" "*.Mask")))'
FAKE = '''#!/usr/bin/env python3
import json, sys
if len(sys.argv) != 3 or sys.argv[1] != "lint": sys.exit(2)
v = json.load(open(sys.argv[2]))
bad = [f for f in v.get("fasteners", []) if not f.get("tool")]
print(json.dumps({"schemaVersion": 1, "passed": not bad and bool(v.get("fasteners")), "findings": [{"severity": "error", "id": f.get("id", "?"), "check": "tool", "hint": "Specify the actual tool."} for f in bad], "animationRequired": False}))
sys.exit(1 if bad or not v.get("fasteners") else 0)
'''
with tempfile.TemporaryDirectory(prefix='aiflow-review-') as td:
p = pathlib.Path(td)
tools = p / 'tools'; tools.mkdir(); fake = tools / 'adom-aiflow-mechanical'; fake.write_text(FAKE); fake.chmod(0o755)
base = {k: v for k, v in os.environ.items() if k not in ('AIFLOW_MECHANICAL_SRC', 'AIFLOW_WIRING_SRC')}
bare = dict(base, AIFLOW_DASHBOARD='off', PATH='/usr/bin:/bin')
with_tool = dict(bare, PATH=f'{tools}:/usr/bin:/bin')
assert not any((pathlib.Path(d) / 'adom-aiflow-mechanical').exists() for d in ('/usr/bin', '/bin'))
def call(run, *args, env=bare):
return subprocess.run([binary, '--run', str(run), '--ai-thread', 'review-test', *args], env=env, capture_output=True, text=True, timeout=60)
def attach(run, board, spec):
state = run / 'run.json'; d = json.loads(state.read_text()); d['boards'] = [str(board)]; d['spec'] = str(spec); state.write_text(json.dumps(d))
def data(run): return json.loads((run / 'run.json').read_text())
board = p / 'plate.kicad_pcb'; board.write_text(HEAD + hole(15) + ')\n')
spec = p / 'spec.json'; spec.write_text(json.dumps({'mechanicalReview': {'schemaVersion': 1, 'input': 'mech.json', 'refs': ['H1']}}))
mech = p / 'mech.json'
run = p / 'declared'
assert call(run, 'intake', '--prompt-time', '2026-10-10T00:00:00Z', '--engine', 'test', '--kind', 'pattern').returncode == 0
attach(run, board, spec)
r = call(run, 'step', 'rules'); assert r.returncode == 0, (r.stdout, r.stderr)
seq = data(run)['sequence']; assert seq.index('mechanical-review') == seq.index('finish') - 1 and 'wiring-review' not in seq, seq
r = call(run, 'finish'); out = r.stdout + r.stderr
assert r.returncode == 0 and 'mechanical review not recorded' in out, out
# tool not installed: a clear refusal that says it is unreleased and names the repo
mech.write_text(json.dumps({'schemaVersion': 1, 'fasteners': [{'id': 'H1-screw', 'ref': 'H1', 'tool': '2.5 mm hex'}]}))
r = call(run, 'review', 'mechanical'); out = r.stdout + r.stderr
assert r.returncode != 0 and 'not installed' in out and 'adom/adom-aiflow-mechanical' in out and 'unreleased' in out and 'AIFLOW_MECHANICAL_SRC' in out, out
# a review file without the contract version is refused before the tool runs
mech.write_text(json.dumps({'fasteners': [{'id': 'H1-screw', 'tool': 'x'}]}))
r = call(run, 'review', 'mechanical', env=with_tool); assert r.returncode != 0 and 'schemaVersion' in r.stdout + r.stderr, r.stdout + r.stderr
# a review naming a part the board does not have is stale
mech.write_text(json.dumps({'schemaVersion': 1, 'fasteners': [{'id': 's', 'ref': 'H7', 'tool': 'x'}]}))
r = call(run, 'review', 'mechanical', env=with_tool); assert r.returncode != 0 and 'H7' in r.stdout + r.stderr, r.stdout + r.stderr
# a failing review records nothing
mech.write_text(json.dumps({'schemaVersion': 1, 'fasteners': [{'id': 'H1-screw', 'ref': 'H1'}]}))
r = call(run, 'review', 'mechanical', env=with_tool); out = r.stdout + r.stderr
assert r.returncode == 0 and 'FINDINGS' in out and 'did not pass' in out and 'Specify the actual tool' in out, out
assert 'mechanical-review' not in data(run)['outcomes']
# a passing review, during its step, bound to the review file, spec and board
mech.write_text(json.dumps({'schemaVersion': 1, 'fasteners': [{'id': 'H1-screw', 'ref': 'H1', 'tool': '2.5 mm hex'}]}))
d = data(run); d['steps']['mechanical-review'] = {'visits': [{'at': '2026-10-10T00:00:01Z'}]}; d['currentStep'] = 'mechanical-review'; (run / 'run.json').write_text(json.dumps(d))
r = call(run, 'review', 'mechanical', env=with_tool); assert r.returncode == 0, (r.stdout, r.stderr)
o = data(run)['outcomes']['mechanical-review']
assert o['pass'] and o['schemaVersion'] == 1 and o['boardSha256'] and o['inputSha256'] and o['specSha256'] and o['refs'] == ['H1'], o
assert [f['ref'] for f in o['serviceFeatures']] == ['H1'] and o['tool']['mode'] == 'path', o
r = call(run, 'finish'); out = r.stdout + r.stderr
assert r.returncode == 0 and 'mechanical review' not in out and 'native DRC' in out, out
# the review file edited afterwards: stale
mech.write_text(json.dumps({'schemaVersion': 1, 'fasteners': [{'id': 'H1-screw', 'ref': 'H1', 'tool': '3 mm hex'}]}))
r = call(run, 'finish'); assert 'review file changed' in r.stdout + r.stderr, r.stdout + r.stderr
mech.write_text(json.dumps({'schemaVersion': 1, 'fasteners': [{'id': 'H1-screw', 'ref': 'H1', 'tool': '2.5 mm hex'}]}))
r = call(run, 'finish'); assert 'mechanical review' not in r.stdout + r.stderr, r.stdout + r.stderr
# a different board (the hole moved): the review no longer counts
moved = p / 'moved.kicad_pcb'; moved.write_text(HEAD + hole(16) + ')\n')
d = data(run); d['boards'].append(str(moved)); (run / 'run.json').write_text(json.dumps(d))
r = call(run, 'finish'); out = r.stdout + r.stderr
assert r.returncode == 0 and 'mechanical review is of another board' in out, out
# not declared: no review step, finish says nothing about reviews
plain = p / 'plain'; empty = p / 'empty.json'; empty.write_text('{}')
assert call(plain, 'intake', '--prompt-time', '2026-10-10T00:00:00Z', '--engine', 'test', '--kind', 'pattern').returncode == 0
attach(plain, board, empty)
assert call(plain, 'step', 'rules').returncode == 0
assert data(plain)['sequence'] == ['intake', 'rules', 'models', 'outline', 'silkscreen', '3d', 'finish', 'export'], data(plain)['sequence']
r = call(plain, 'finish'); open_items = data(plain)['summary']['open']; assert not any('review' in x.lower() for x in open_items), open_items
# an undeclared review can still be run (advisory), it says finish does not need it
r = call(plain, 'review', 'mechanical', '--input', str(mech), env=with_tool); assert r.returncode == 0 and 'Not declared' in r.stdout + r.stderr, r.stdout + r.stderr
assert data(plain)['sequence'] == ['intake', 'rules', 'models', 'outline', 'silkscreen', '3d', 'finish', 'export']
print('PASS: declared review is a step before finish, missing/failing/stale reviews and a missing tool are refused, a passing bound review satisfies finish, nothing changes when undeclared')