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
Publish 0.2.36
7 files changed
+91−16
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -25,7 +25,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "serde",@@ -35,7 +35,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.2.35"+version = "0.2.36" dependencies = [ "roxmltree", "serde",@@ -44,7 +44,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "serde",@@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "aiflow-grid",@@ -63,7 +63,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "serde",@@ -72,7 +72,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "serde",@@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "aiflow-copper",@@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.2.35"+version = "0.2.36" dependencies = [ "aiflow-board", "aiflow-grid",@@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.2.35"+version = "0.2.36" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.2.35"+version = "0.2.36" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/programming.rs+77−2@@ -69,6 +69,48 @@ fn reachable(b: &Board, reference: &str) -> Result<(), String> { if tp { Err(format!("{reference} is a test pad with no silkscreen label")) } else { Err(format!("{reference} ({}) is neither a machine contact nor a labelled test pad", fp.lib.rsplit(':').next().unwrap_or(&fp.lib))) } } +/// Cable access is an explicit alternative to a workcell fixture, not a declaration+/// that arbitrary IC pads are reachable. Check declared connector pins against saved copper.+fn cable_usb_check(b: &Board, rec: &Value) -> Result<(), Vec<String>> {+ let mut problems = Vec::new();+ let connector = rec["connector"].as_str().unwrap_or("");+ let target_ref = rec["targetRef"].as_str().unwrap_or("");+ // the evidence is the declared pads carrying D+, D-, GND and VBUS (checked below), not the+ // connector's name: USB1, CN1 or a project-library footprint are as valid as J1 USB_C+ if !b.footprints.contains_key(connector) || connector == target_ref {+ problems.push("connector must name the board's USB connector footprint (not the target chip)".into());+ }+ let target = rec["target"].as_str().unwrap_or("");+ if !targets(b).iter().any(|(r, m)| r == target_ref && m == target && m.starts_with("ESP32")) {+ problems.push("targetRef/target must match the programmable ESP32 part on the board".into());+ }+ if rec["boot"].as_str().map_or(true, |x| x.trim().is_empty()) {+ problems.push("boot recovery instructions missing".into());+ }+ let mut nets = Vec::new();+ for signal in ["D+", "D-", "GND", "VBUS"] {+ let pin = &rec["connectorPins"][signal];+ let net = pin["net"].as_str().unwrap_or("");+ let names = pin["pads"].as_array();+ if net.is_empty() || names.map_or(true, |a| a.is_empty()) {+ problems.push(format!("connectorPins.{signal} needs nonempty pads and net"));+ continue;+ }+ for name in names.unwrap() {+ let pad = name.as_str().unwrap_or("");+ if !b.pads.iter().any(|p| p.reference == connector && p.name == pad && p.net == net) {+ problems.push(format!("{signal}: {connector}.{pad} does not carry declared net {net}"));+ }+ }+ nets.push(net);+ }+ nets.sort(); nets.dedup();+ if nets.len() != 4 { problems.push("USB data, ground and VBUS must be four distinct nets".into()); }+ // Pin membership is evidence of the connector mapping only. Full connectivity,+ // MCU routing and programming behavior remain the native gate and bench review.+ if problems.is_empty() { Ok(()) } else { Err(problems) }+}+ /// The finish gate: Ok when the board has no programmable part or its record is complete and every pin /// is reachable; Err(problems) otherwise. pub fn check(board_path: &str, b: &Board, spec: &Value) -> Result<(), Vec<String>> {@@ -76,6 +118,7 @@ pub fn check(board_path: &str, b: &Board, spec: &Value) -> Result<(), Vec<String if t.is_empty() { return Ok(()); } let names = t.iter().map(|(r, m)| format!("{r} {m}")).collect::<Vec<_>>().join(", "); let Some(rec) = record(board_path, spec) else { return Err(vec![format!("no `programming` entry in board-release.json for {names}")]) };+ if rec["interface"] == "usb-serial-jtag" && rec["access"] == "usb-cable" { return cable_usb_check(b, &rec); } let mut p = Vec::new(); let iface = rec["interface"].as_str().unwrap_or(""); let pins = rec["pins"].as_object().cloned().unwrap_or_default();@@ -99,7 +142,7 @@ pub fn summary(board_path: &str, b: &Board, spec: &Value, result: &Result<(), Ve if t.is_empty() { return "programming: no programmable part: programming not required".into(); } let names = t.iter().map(|(r, m)| format!("{r} {m}")).collect::<Vec<_>>().join(", "); match result {- Ok(()) => format!("programming: {names}, interface {} on reachable pins", record(board_path, spec).and_then(|r| r["interface"].as_str().map(str::to_string)).unwrap_or_default()),+ Ok(()) => format!("programming: {names}, interface {} with declared access checked", record(board_path, spec).and_then(|r| r["interface"].as_str().map(str::to_string)).unwrap_or_default()), Err(p) => format!("programming: {names}, {} problem(s) (the gate below)", p.len()), } }@@ -177,7 +220,39 @@ mod tests { assert_eq!(summary("/nonexistent/b.kicad_pcb", &b, &json!({}), &Ok(())), "programming: no programmable part: programming not required"); let b = Board::from_text("/nonexistent/b.kicad_pcb", BOARD.to_string()).unwrap(); let spec = json!({"programming": {"interface": "swd"}});- assert_eq!(summary("/nonexistent/b.kicad_pcb", &b, &spec, &Ok(())), "programming: U1 STM32G431CBT6, interface swd on reachable pins");+ assert_eq!(summary("/nonexistent/b.kicad_pcb", &b, &spec, &Ok(())), "programming: U1 STM32G431CBT6, interface swd with declared access checked"); assert!(summary("/nonexistent/b.kicad_pcb", &b, &spec, &Err(vec!["x".into()])).ends_with("1 problem(s) (the gate below)")); }+ #[test]+ fn usb_serial_jtag_cable_checks_real_connector_pins_and_target() {+ let txt = BOARD.replace("STM32G431CBT6", "ESP32-S3-MINI-1U-N8");+ let txt = txt.replacen("(net 0 \"\")", "(net 0 \"\") (net 6 \"DP\") (net 7 \"DM\") (net 8 \"VBUS\")", 1);+ let at = txt.rfind(')').unwrap();+ let extra = r#"(footprint "Connector_USB:USB_C_Test" (layer "F.Cu") (at 10 10)+ (property "Reference" "J1")+ (pad "A6" smd rect (at 0 0) (size 1 1) (layers "F.Cu") (net 6 "DP"))+ (pad "A7" smd rect (at 1 0) (size 1 1) (layers "F.Cu") (net 7 "DM"))+ (pad "A1" smd rect (at 2 0) (size 1 1) (layers "F.Cu") (net 4 "GND"))+ (pad "A4" smd rect (at 3 0) (size 1 1) (layers "F.Cu") (net 8 "VBUS")))"#;+ let txt = format!("{}{}{}", &txt[..at], extra, &txt[at..]);+ let b = Board::from_text("/nonexistent/b.kicad_pcb", txt).unwrap();+ let mut spec = json!({"programming": {"interface":"usb-serial-jtag", "access":"usb-cable",+ "connector":"J1", "targetRef":"U1", "target":"ESP32-S3-MINI-1U-N8", "boot":"Hold BOOT, reset, release BOOT",+ "connectorPins":{"D+":{"pads":["A6"],"net":"DP"},"D-":{"pads":["A7"],"net":"DM"},+ "GND":{"pads":["A1"],"net":"GND"},"VBUS":{"pads":["A4"],"net":"VBUS"}}}});+ assert!(check("/nonexistent/b.kicad_pcb", &b, &spec).is_ok());+ spec["programming"]["connectorPins"]["D+"]["net"] = json!("DM");+ assert!(check("/nonexistent/b.kicad_pcb", &b, &spec).is_err());+ spec["programming"]["connectorPins"]["D+"]["net"] = json!("DP");+ spec["programming"]["connector"] = json!("U1");+ assert!(check("/nonexistent/b.kicad_pcb", &b, &spec).is_err());+ // the connector's name is not evidence: a project-library footprint renamed CN1 still passes+ let renamed = Board::from_text("/nonexistent/b.kicad_pcb", b.text.replace("Connector_USB:USB_C_Test", "MyLib:TypeC_16P").replace("\"Reference\" \"J1\"", "\"Reference\" \"CN1\"")).unwrap();+ spec["programming"]["connector"] = json!("CN1");+ assert!(check("/nonexistent/b.kicad_pcb", &renamed, &spec).is_ok());+ spec["programming"]["connector"] = json!("J1");+ spec["programming"]["targetRef"] = json!("U2");+ assert!(check("/nonexistent/b.kicad_pcb", &b, &spec).is_err());+ }+ }
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.2.35",+ "version": "0.2.36", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad, Fusion 360 and Altium Designer today (the same steps and gates through each tool's bridge); Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
page.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.2.35",+ "version": "0.2.36", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad, Fusion 360 and Altium Designer today (the same steps and gates through each tool's bridge); Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
skills/aiflow-molecule/SKILL.md+1−1@@ -198,4 +198,4 @@ Record it as `programming` in board-release.json beside the board (or `programmi {"interface": "swd", "pins": {"SWDIO": "J8", "SWCLK": "J9", "NRST": "J10", "GND": "J7"}, "target": "STM32G431CBT6", "boot": "BOOT0 low via R2; JP1 bridges for DFU"} ``` -`interface` is `swd`, `uart-boot` or `usb-dfu`; `pins` maps each interface signal to the reference that carries it; `target` is the MPN being programmed. `step schematic` and `step placement` print the Hint; `finish` refuses the board when a programmable part has no `programming` record, a required pin is missing, or a pin is neither a machine contact nor a labelled test pad.+`interface` is `swd`, `uart-boot` or `usb-dfu`; `pins` maps each interface signal to the reference that carries it; `target` is the MPN being programmed. A board programmed over its own USB connector instead (an ESP32-S3 or C3 with native USB Serial/JTAG) declares `"interface": "usb-serial-jtag", "access": "usb-cable"` with `connector` (the USB connector's reference), `targetRef` and `target` (the ESP32), `boot` (how to force download mode) and `connectorPins` mapping `D+`, `D-`, `GND` and `VBUS` to `{"pads": [...], "net": "..."}`; the check confirms those pads carry those four distinct nets, and no robot contacts are needed. `step schematic` and `step placement` print the Hint; `finish` refuses the board when a programmable part has no `programming` record, a required pin is missing, or a pin is neither a machine contact nor a labelled test pad.