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.
← Commit history
Record current-project rules checks without overwriting custom fabrication rules
3 files changed
+67−1
SKILL.md+4@@ -439,3 +439,7 @@ Read `skills/aiflow-project-page/SKILL.md` and run `adom-aiflow project-page-che For multiple-editor IPC conflicts, read `docs/isolated-kicad-read-socket.md`. `ADOM_AIFLOW_KICAD_READ_SOCKET` selects a verified instance for read/validation and zone-measurement verbs; zone measurement includes its single refill request. It does not redirect route, placement, zone-edit or window-capture calls. Preserve unsaved editors, require live-editor identity, and keep every native DRC gate. A renamed PCB checkpoint must carry matching project/rule sidecars before it is opened. Copper measurement uses the same explicit `ADOM_AIFLOW_KICAD_READ_SOCKET` as placement/routing reads. This includes its one native refill request and delayed zone snapshots; it does not enable route or placement edits on isolated endpoints. See `docs/isolated-kicad-read-socket.md`.++### Rechecking an adopted project's design rules++After board adoption, `rules check` records a fresh input-bound result when checking the run's current board and the profile named by its spec. It preserves the existing `.kicad_dru`, including justified local constraints. Checking an unrelated board or overriding the spec's profile does not qualify this run. A failure is recorded as failed evidence; native DRC and fabrication review remain required. Do not re-install or overwrite custom rules just to clear a stale timeline requirement.
crates/adom-aiflow/src/main.rs+28−1@@ -3783,7 +3783,34 @@ fn main() { ok(&format!("installed the jlcpcb-2L rules (JLCPCB published 2-layer capabilities, cited in the file) at {}", dru.display()), &hints); } _ => {- match checks::rules_check(&profile, &board_path) {+ let result = checks::rules_check(&profile, &board_path);+ // A read-only check of this run's selected project rules is proof too.+ // Do not rewrite custom rules, or qualify a different board/profile.+ if let Some(mut r) = run {+ let same_board = std::fs::canonicalize(&board_path).ok()+ .zip(std::fs::canonicalize(r.current_board()).ok())+ .is_some_and(|(a, b)| a == b);+ let same_profile = checks::rules_profile(&spec_of(&r)).as_deref()+ == Some(profile.as_str());+ let dru = Path::new(&board_path).with_extension("kicad_dru");+ let selected_dru = Path::new(&r.current_board()).with_extension("kicad_dru");+ let same_rules = dru == selected_dru || std::fs::canonicalize(&dru).ok()+ .zip(std::fs::canonicalize(&selected_dru).ok())+ .is_some_and(|(a, b)| a == b);+ if same_board && same_profile && same_rules {+ r.outcome("rules", json!({+ "pass": result.is_ok(), "profile": profile,+ "method": "existing-project-check", "board": board_path,+ "boardSha256": sha256_file(&board_path),+ "file": dru.display().to_string(),+ "rulesSha256": sha256_file(&dru.display().to_string()),+ "note": result.as_ref().map(|m| m.as_str())+ .unwrap_or_else(|e| e.as_str())+ }));+ r.save().unwrap_or_else(|e| err(&e.to_string(), &[]));+ }+ }+ match result { Ok(m) => ok(&m, &[]), Err(e) => err(&e, &[checks::rules_hint(&profile)]), }
tests/rules-workflow.pyadded+35@@ -0,0 +1,35 @@+"""Existing project rules produce honest run evidence without mutating rule files."""+import hashlib,json,os,pathlib,subprocess,sys,tempfile+binary=str(pathlib.Path(sys.argv[1]).resolve())+with tempfile.TemporaryDirectory(prefix='aiflow-rules-cli-') as td:+ p=pathlib.Path(td);run=p/'run';env=dict(os.environ,AIFLOW_DASHBOARD='off')+ def call(*args):+ return subprocess.run([binary,'--run',str(run),'--ai-thread','rules-regression',*args],env=env,capture_output=True,text=True,timeout=30)+ assert call('intake','--prompt-time','2026-10-10T00:00:00Z','--engine','test').returncode==0+ board=p/'board.kicad_pcb';board.write_text('(kicad_pcb (version 20240108))\n')+ rules=board.with_suffix('.kicad_dru');body='(version 1)\n(rule "project custom" (constraint clearance (min 0.2mm)))\n';rules.write_text(body)+ spec=p/'spec.json';spec.write_text(json.dumps({'fab':{'rules':'fab'}}))+ state=run/'run.json';d=json.loads(state.read_text());d['boards']=[str(board)];d['spec']=str(spec);d['sequence']=['rules'];d['steps']={'rules':{'visits':[{'at':'2026-10-10T00:00:00Z'}]}};state.write_text(json.dumps(d))+ def outcome():return json.loads(state.read_text())['outcomes'].get('rules')+ r=call('rules','check');assert r.returncode==0,(r.stdout,r.stderr)+ o=outcome();assert o['pass'] and o['method']=='existing-project-check'+ assert o['rulesSha256']==hashlib.sha256(body.encode()).hexdigest()+ assert o['boardSha256']==hashlib.sha256(board.read_bytes()).hexdigest()+ assert rules.read_text()==body+ g=json.loads(call('finish-guard','--json').stdout);assert 'rules' not in g['remaining'] and not g['engineeringComplete']+ other=p/'other.kicad_pcb';other.write_text(board.read_text());other.with_suffix('.kicad_dru').write_text(body)+ assert call('rules','check','--board',str(other)).returncode==0+ assert outcome()==o,'unrelated board changed this run proof'+ r=call('rules','check','--profile','jlcpcb');assert r.returncode!=0+ assert outcome()==o,'profile override changed selected-profile proof'+ alias=p/'same-board.kicad_pcb';alias.symlink_to(board);alias.with_suffix('.kicad_dru').write_text(body)+ assert call('rules','check','--board',str(alias)).returncode==0+ assert outcome()==o,'alias with a different rules file changed the selected project proof'+ alias.with_suffix('.kicad_dru').unlink();alias.with_suffix('.kicad_dru').symlink_to(rules)+ assert call('rules','check','--board',str(alias)).returncode==0+ assert outcome()['pass']+ rules.unlink();r=call('rules','check');assert r.returncode!=0+ assert outcome()['pass'] is False,'failed recheck left a stale passing outcome'+ g=json.loads(call('finish-guard','--json').stdout);assert any('rules' in x for x in g['remaining']) and not g['mayStop']+ assert not rules.exists(),'check rewrote rules'+ print('PASS: current-board proof, immutable custom rules, hashes, unrelated-board/profile isolation, alias binding and stale-success invalidation; engineering gates remain open.')