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.58
e8180ab
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
"""`parts` through the CLI against a fake adom-wiki on PATH (no network):
issue #161: an ordering code with a "/" packaging suffix (MCP73831T-2ACI/OT) finds the part factory's page
for the MPN without it (adom/mcp73831t-2aci), says how it matched, and a page that keeps the suffix
(adom/sp485een-l-tr) is still found by its own slug first;
issue #157: a project-library entry for an MPN that also has a wiki page is named (page used), and with
"prefer": true in provenance.json the library entry wins (wiki null, wikiOverridden kept);
issue #178: a BOM `wiki` column names a row's page (adom/adom-1578), and a page under another slug that
declares the MPN in component.mpn (adom/adom-1317 for MPN 1317) is found when the slug forms miss.
python3 tests/parts-sources-workflow.py <abs path to target/release/adom-aiflow>
"""
import json, os, pathlib, subprocess, sys, tempfile
binary = str(pathlib.Path(sys.argv[1]).resolve())
# what `adom-wiki search <q> --json` answered on 2026-11-06: the factory page is ranked by phrase only
FAKE_WIKI = r'''#!/usr/bin/env python3
import json, sys
a = [x for x in sys.argv[1:] if x != "--json"]
H = lambda slug, title, tier: {"type": "hardware", "owner": "adom", "slug": slug, "title": title, "match": {"tier": tier}}
if a[:1] == ["search"]:
q = a[1].upper()
hits = []
if q.startswith("MCP73831"):
hits = [H("mcp73831", "MCP73831", "phrase"), H("mcp73831t-2aci", "MCP73831T-2ACI", "phrase")]
elif q.startswith("SP485EEN"):
hits = [H("sp485een-l", "SP485EEN-L", "phrase"), H("sp485een-l-tr", "SP485EEN-L/TR", "exact_title" if q == "SP485EEN-L/TR" else "phrase")]
elif q == "ABC-123":
hits = [H("abc-123", "ABC-123", "exact_slug")]
elif q == "1317":
# issue #178: the battery's page is adom/adom-1317 (a bare numeric slug is a poor page name)
hits = [H("adom-1317", "Adafruit 1317 LiPo battery", "phrase"), H("adom-13170", "Other", "phrase")]
print(json.dumps({"status": "ok", "data": {"results": hits}})); sys.exit(0)
if a[:2] == ["repo", "show"] and a[3] == "page.json":
pages = {"adom/adom-1317": {"component": {"mpn": "1317"}}, "adom/adom-1578": {"component": {"mpn": "1578"}}}
if a[2] in pages: print(json.dumps(pages[a[2]])); sys.exit(0)
print(json.dumps({"status": "error", "error": "not found"})); sys.exit(1)
print(json.dumps({"status": "error", "error": "fake adom-wiki: " + " ".join(a)})); sys.exit(2)
'''
with tempfile.TemporaryDirectory(prefix='aiflow-parts-sources-') as td:
p = pathlib.Path(td)
b = p / 'bin'; b.mkdir()
(b / 'adom-wiki').write_text(FAKE_WIKI); (b / 'adom-wiki').chmod(0o755)
(b / 'adom-cli').write_text('#!/bin/sh\nexit 0\n'); (b / 'adom-cli').chmod(0o755)
env = dict(os.environ, AIFLOW_DASHBOARD='off', PATH=f"{b}:{os.environ['PATH']}")
def call(run, *args):
return subprocess.run([binary, '--run', str(run), '--ai-thread', 'parts-sources-test', *args], env=env, capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL)
def parts(run): return json.loads((run / 'run.json').read_text())['outcomes']['parts']
bom = p / 'bom.csv'
bom.write_text('ref,value,mpn,manufacturer\nU1,charger,MCP73831T-2ACI/OT,Microchip\nU2,RS-485,SP485EEN-L/TR,MaxLinear\nU3,thing,ABC-123,Acme\n')
# --- #161: the "/OT" ordering code finds the factory page for its base MPN ---------------------------
run = p / 'run'
assert call(run, 'intake', '--prompt-time', '2026-11-06T00:00:00Z', '--engine', 'test').returncode == 0
r = call(run, 'parts', '--bom', str(bom)); out = r.stdout + r.stderr
assert r.returncode == 0, out
rows = {x['mpn']: x for x in parts(run)['parts']}
assert rows['MCP73831T-2ACI/OT']['wiki'] == 'adom/mcp73831t-2aci', rows
assert 'without its /OT packaging suffix' in rows['MCP73831T-2ACI/OT']['wikiMatch'], rows
assert rows['SP485EEN-L/TR']['wiki'] == 'adom/sp485een-l-tr' and 'wikiMatch' not in rows['SP485EEN-L/TR'], rows
assert 'MCP73831T-2ACI/OT: adom/mcp73831t-2aci is the page for MCP73831T-2ACI' in out, out
assert parts(run)['missing'] == [], parts(run)
# --- #157: a project-library entry for an MPN that has a page --------------------------------------
lib = p / 'lib'; (lib / 'lib.pretty').mkdir(parents=True); (lib / '3dmodels').mkdir()
(lib / 'lib.kicad_sym').write_text('(kicad_symbol_lib (version 20241209) (generator "x")\n (symbol "ABC-123" (property "Reference" "U") (property "Footprint" "lib:QFN-16"))\n)\n')
(lib / 'lib.pretty' / 'QFN-16.kicad_mod').write_text('(footprint "QFN-16" (layer "F.Cu") (model "${KIPRJMOD}/3dmodels/ABC-123.step"))\n')
(lib / '3dmodels' / 'ABC-123.step').write_text('ISO-10303-21;\nHEADER;\nENDSEC;\n')
prov = lib / 'provenance.json'
prov.write_text(json.dumps({'parts': [{'mpn': 'ABC-123', 'source': 'datasheet rev C, fig. 12'}]}))
r = call(run, 'parts', '--bom', str(bom), '--project-lib', str(lib)); out = r.stdout + r.stderr
assert r.returncode == 0, out
rows = {x['mpn']: x for x in parts(run)['parts']}
assert rows['ABC-123']['wiki'] == 'adom/abc-123' and 'projectLib' not in rows['ABC-123'], rows
assert 'both the wiki page adom/abc-123 and a complete project-library entry' in out and '"prefer": true' in out, out
prov.write_text(json.dumps({'parts': [{'mpn': 'ABC-123', 'source': 'datasheet rev C, fig. 12', 'prefer': True}]}))
r = call(run, 'parts', '--bom', str(bom), '--project-lib', str(lib)); out = r.stdout + r.stderr
assert r.returncode == 0, out
rec = parts(run); rows = {x['mpn']: x for x in rec['parts']}
assert rows['ABC-123']['wiki'] is None and rows['ABC-123']['wikiOverridden'] == 'adom/abc-123', rows
assert rows['ABC-123']['projectLib']['footprint'] == 'QFN-16', rows
assert rec['projectLibParts'] == ['ABC-123'], rec
assert 'project library (preferred over adom/abc-123)' in out, out
assert any('project library\'s entry is used' in n for n in rec['sourceNotes']), rec
# --- #178: a BOM row names its page; else a page declaring the MPN in component.mpn is found ---------
bom2 = p / 'bom2.csv'
bom2.write_text('ref,mpn,placement,wiki\nBT1,1578,off-board,adom/adom-1578\nBT2,1317,off-board,\nBT3,9999,off-board,adom/no-such-page\n')
run2 = p / 'run2'
assert call(run2, 'intake', '--prompt-time', '2026-11-06T00:00:00Z', '--engine', 'test').returncode == 0
r = call(run2, 'parts', '--bom', str(bom2)); out = r.stdout + r.stderr
assert r.returncode == 0, out
rec = parts(run2); rows = {x['mpn']: x for x in rec['parts']}
assert rows['1578']['wiki'] == 'adom/adom-1578' and 'named in the BOM' in rows['1578']['wikiMatch'], rows
assert rows['1317']['wiki'] == 'adom/adom-1317' and 'component.mpn' in rows['1317']['wikiMatch'], rows
assert rows['9999']['wiki'] is None and 'could not be read' in rows['9999']['wikiMatch'], rows
assert rec['missing'] == ['9999'], rec
assert 'adom/adom-1578 is named in the BOM' in out, out
print('parts-sources-workflow: OK')