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.16
ce048dd
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
"""Issue #85 through the CLI: `--page owner/slug --page-dir <folder>` puts every push of the run under
that folder (the run README at <folder>/docs/runs/<id>/, the clips page at <folder>/docs/clips.md, its
media in <folder>/docs/clips/), the URLs it writes carry the folder, a bad folder is refused, and
`report` with no page writes the README locally and pushes nothing. adom-wiki is a fake on PATH that
records every `repo push --files` and checks each file exists in the push's working directory.
No CAD, desktop or dashboard.
python3 tests/page-dir-workflow.py <adom-aiflow binary>
"""
import json, os, pathlib, subprocess, sys, tempfile
binary = str(pathlib.Path(sys.argv[1]).resolve())
FAKE_WIKI = r'''#!/usr/bin/env python3
import json, os, sys
log = os.environ["FAKE_WIKI_LOG"]
a = sys.argv[1:]
rec = {"args": a, "cwd": os.getcwd()}
if a[:2] == ["repo", "push"]:
i = a.index("--files"); files = a[i + 1:]
if "-m" in files: files = files[:files.index("-m")]
rec["files"] = files; rec["missing"] = [f for f in files if not os.path.isfile(os.path.join(os.getcwd(), f))]
rec["texts"] = {f: open(os.path.join(os.getcwd(), f)).read() for f in files if f.endswith(".md") and f not in rec["missing"]}
open(log, "a").write(json.dumps(rec) + "\n")
if a[:2] == ["repo", "push"]:
print(json.dumps({"status": "ok", "commit": "abc1234"})); sys.exit(1 if rec["missing"] else 0)
if a[:2] == ["repo", "show"]:
sys.exit(1)
sys.exit(0)
'''
with tempfile.TemporaryDirectory(prefix='aiflow-page-dir-') as td:
p = pathlib.Path(td)
b = p / 'bin'; b.mkdir()
(b / 'adom-wiki').write_text(FAKE_WIKI); (b / 'adom-wiki').chmod(0o755)
log = p / 'wiki.jsonl'
env = dict(os.environ, AIFLOW_DASHBOARD='off', PATH=f"{b}:{os.environ['PATH']}", FAKE_WIKI_LOG=str(log))
def call(run, *args):
return subprocess.run([binary, '--run', str(run), '--ai-thread', 'page-dir-test', *args], env=env, capture_output=True, text=True, timeout=60)
def pushes():
return [json.loads(l) for l in log.read_text().splitlines() if '"push"' in l] if log.exists() else []
def data(run): return json.loads((run / 'run.json').read_text())
# a bad folder is refused before a run is made
for bad in ['/rig', 'rig/../x', 'rig//x']:
r = call(p / 'bad', 'intake', '--prompt-time', '2026-10-21T01:00:00Z', '--engine', 'test', '--page', 'john/enc', '--page-dir', bad)
assert r.returncode != 0 and '--page-dir' in r.stdout + r.stderr, (bad, r.stdout, r.stderr)
assert not (p / 'bad' / 'run.json').exists()
r = call(p / 'nopage', 'intake', '--prompt-time', '2026-10-21T01:00:00Z', '--engine', 'test', '--page-dir', 'rig')
assert r.returncode != 0 and 'needs a page' in r.stdout + r.stderr, (r.stdout, r.stderr)
# report with no page: written locally, nothing pushed
local = p / 'local'
assert call(local, 'intake', '--prompt-time', '2026-10-21T01:00:00Z', '--engine', 'test').returncode == 0
r = call(local, 'report', '--push'); out = r.stdout + r.stderr
assert r.returncode == 0, out
assert (local / 'report' / 'README.md').is_file() and 'written locally' in out and 'nothing pushed' in out, out
assert '# AI Flow run' in (local / 'report' / 'README.md').read_text()
assert pushes() == [], pushes()
# a run in a folder of an existing page
run = p / 'run'; folder = 'rig/boards/rig-carrier'
r = call(run, 'intake', '--prompt-time', '2026-10-21T01:00:00Z', '--engine', 'test', '--page', 'john/enc', '--page-dir', folder + '/')
assert r.returncode == 0, r.stderr
assert data(run)['page'] == 'john/enc' and data(run)['pageDir'] == folder, data(run)
r = call(run, 'report', '--push'); out = r.stdout + r.stderr
assert r.returncode == 0, out
rid = data(run)['reportId']
assert f'https://wiki.adom.inc/john/enc/files/{folder}/docs/runs/{rid}/README.md' in out, out
ps = pushes(); assert ps, 'nothing pushed'
files = [f for x in ps for f in x['files']]
assert all(f.startswith(f'{folder}/docs/runs/{rid}/') for f in files) and not any(x['missing'] for x in ps), ps
assert all(x['args'][2] == 'john/enc' for x in ps)
# the clips page: <folder>/docs/clips.md, media under <folder>/docs/clips/, render paths with the folder
stem = 'window-1-20261021-010000'
for suf in ['.mp4', '-10x.mp4', '-sheet.png', '-poster.jpg']: (run / f'{stem}{suf}').write_bytes(b'x')
d = data(run); d['captures'] = [{'file': str(run / f'{stem}.mp4'), 'step': 'intake', 'visit': 1, 'start': '2026-10-21T01:00:00Z', 'stop': '2026-10-21T01:05:00Z'}]
(run / 'run.json').write_text(json.dumps(d))
log.unlink()
r = call(run, 'clips', '--push'); out = r.stdout + r.stderr
assert r.returncode == 0, out
assert f'https://wiki.adom.inc/john/enc/files/{folder}/docs/clips.md' in out, out
ps = pushes(); files = [f for x in ps for f in x['files']]
assert f'{folder}/docs/clips.md' in files and f'{folder}/docs/clips/{stem}-10x.mp4' in files, files
assert all(f.startswith(f'{folder}/docs/') for f in files) and not any(x['missing'] for x in ps), ps
md = next(x['texts'][f'{folder}/docs/clips.md'] for x in ps if f'{folder}/docs/clips.md' in x.get('texts', {}))
assert f'video="/john/enc/render/{folder}/docs/clips/{stem}-10x.mp4"' in md and 'render/docs/' not in md, md
# the remote clips index is looked up in the folder
shows = [json.loads(l)['args'] for l in log.read_text().splitlines() if '"show"' in l]
assert ['repo', 'show', 'john/enc', f'{folder}/docs/clips.md'] in shows, shows
# clips --page-dir alone moves only the clips page; `.` is the page root
log.unlink()
assert call(run, 'clips', '--page-dir', 'rig/clips-only').returncode == 0
assert data(run)['clipsPage'] == 'john/enc' and data(run)['clipsPageDir'] == 'rig/clips-only' and data(run)['pageDir'] == folder, data(run)
assert call(run, 'report', '--page-dir', '.').returncode == 0 and data(run).get('pageDir') is None
# project-page --sync: the step sub-readmes and the README section in the project page's folder
log.unlink()
d = data(run); d['sequence'] = ['intake', 'rules']; d.setdefault('steps', {}).setdefault('intake', {})['visits'] = [{'at': '2026-10-21T01:00:00Z'}]
(run / 'run.json').write_text(json.dumps(d))
r = call(run, 'project-page', '--sync', '--page', 'john/enc', '--page-dir', folder); out = r.stdout + r.stderr
assert r.returncode == 0 and data(run)['projectPageDir'] == folder, out
ps = pushes(); files = [f for x in ps for f in x['files']]
assert f'{folder}/README.md' in files and f'{folder}/steps/01-intake/README.md' in files, files
assert all(f.startswith(folder + '/') for f in files) and not any(x['missing'] for x in ps), ps
step_md = ps[0]['texts'][f'{folder}/steps/01-intake/README.md']
assert f'video="/john/enc/render/rig/clips-only/docs/clips/{stem}-10x.mp4"' in step_md, step_md
assert call(run, 'project-page', '--page', 'john/enc', '--page-dir', folder, '--create').returncode != 0
print('PASS: --page-dir refused when not a plain relative folder or without a page; report with no page writes <run>/report/README.md and pushes nothing; report and clips push only under the folder with folder URLs and render paths; project-page --sync writes the step pages and README section in the folder; --create with --page-dir refused')