← Commit history
Cargo.lock+10−10
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-aiflow"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-analyze",  "aiflow-board",@@ -25,7 +25,7 @@ dependencies = [  [[package]] name = "aiflow-analyze"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "serde",@@ -35,7 +35,7 @@ dependencies = [  [[package]] name = "aiflow-board"-version = "0.2.37"+version = "0.2.38" dependencies = [  "roxmltree",  "serde",@@ -44,7 +44,7 @@ dependencies = [  [[package]] name = "aiflow-bridge"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "serde",@@ -53,7 +53,7 @@ dependencies = [  [[package]] name = "aiflow-copper"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "aiflow-grid",@@ -63,7 +63,7 @@ dependencies = [  [[package]] name = "aiflow-grid"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "serde",@@ -72,7 +72,7 @@ dependencies = [  [[package]] name = "aiflow-place"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "serde",@@ -81,7 +81,7 @@ dependencies = [  [[package]] name = "aiflow-pours"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "aiflow-copper",@@ -91,7 +91,7 @@ dependencies = [  [[package]] name = "aiflow-router"-version = "0.2.37"+version = "0.2.38" dependencies = [  "aiflow-board",  "aiflow-grid",@@ -101,7 +101,7 @@ dependencies = [  [[package]] name = "aiflow-run"-version = "0.2.37"+version = "0.2.38" dependencies = [  "serde",  "serde_json",
Cargo.toml+1−1
@@ -14,7 +14,7 @@ members = [ ]  [workspace.package]-version = "0.2.37"+version = "0.2.38" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow
⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/mechanical.rs+1−1
@@ -436,7 +436,7 @@ pub fn declared_bodies(b: &Board, spec: &Value) -> (Vec<String>, Vec<String>) { pub(crate) mod tests {     #[test]     fn fusion_renderings_skip_the_courtyard_checks() {-        let dir = std::env::temp_dir().join(format!("aiflow-mech-{}", std::process::id()));+        let dir = std::env::temp_dir().join(format!("aiflow-mech-fusion-{}", std::process::id()));         std::fs::create_dir_all(&dir).unwrap();         let fusion = dir.join("board-0.kicad_pcb");         std::fs::write(&fusion, "(kicad_pcb (version 20241229) (generator \"adom-aiflow-fusion\") (generator_version \"1\")\n)").unwrap();
crates/adom-aiflow/src/programming.rs+18−9
@@ -160,18 +160,20 @@ fn check_list(b: &Board, t: &[(String, String)], list: &[Value]) -> Result<(), V fn tag_connect_check(b: &Board, rec: &Value) -> Result<(), Vec<String>> {     let mut problems = Vec::new();     let connector = rec["connector"].as_str().unwrap_or("");-    match b.footprints.get(connector) {-        Some(fp) if fp.lib.contains("Tag-Connect") || fp.lib.contains("TC2030") || fp.lib.contains("TC2050") => {}-        Some(fp) => problems.push(format!("connector {connector} ({}) is not a Tag-Connect footprint", fp.lib.rsplit(':').next().unwrap_or(&fp.lib))),-        None => problems.push(format!("connector {connector} is not on the board")),-    }     let target_ref = rec["targetRef"].as_str().unwrap_or("");+    // the evidence is the declared pads carrying the SWD nets into the part (below), not the footprint's+    // name: a project-library pogo-pin pad set is as valid a cable landing as a TC2030 (as for usb-cable)+    if !b.footprints.contains_key(connector) || connector == target_ref {+        problems.push(format!("connector {connector} must be the board's cable footprint (not the target chip)"));+    }     if !targets(b).iter().any(|(r, m)| r == target_ref && Some(m.as_str()) == rec["target"].as_str()) {         problems.push("targetRef/target must match a programmable part on the board".into());     }     let mut nets = Vec::new();     for signal in ["SWDIO", "SWCLK", "NRST", "GND"] {         let pin = &rec["connectorPins"][signal];+        // NRST is optional: plenty of SWD hookups leave reset unwired; when declared it is checked+        if signal == "NRST" && pin.is_null() { continue; }         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; }@@ -183,8 +185,9 @@ fn tag_connect_check(b: &Board, rec: &Value) -> Result<(), Vec<String>> {         if signal != "GND" && !b.pads.iter().any(|p| p.reference == target_ref && p.net == net) { problems.push(format!("{signal}: net {net} does not reach {target_ref}")); }         nets.push(net);     }+    let declared = nets.len();     nets.sort(); nets.dedup();-    if nets.len() != 4 { problems.push("SWDIO, SWCLK, NRST and GND must be four distinct nets".into()); }+    if nets.len() != declared { problems.push("SWDIO, SWCLK, GND (and NRST when declared) must be distinct nets".into()); }     if problems.is_empty() { Ok(()) } else { Err(problems) } } @@ -294,9 +297,15 @@ mod tests {         // U2's SWD nets do not reach J7: refused for U2         let both = json!({"programming": [tc("U1", "J7"), tc("U2", "J7")]});         assert!(check("t.kicad_pcb", &b, &both).unwrap_err().iter().any(|e| e.contains("U2") && e.contains("does not reach U2")));-        // a non-Tag-Connect footprint is not a cable landing-        let mut notc = tc("U1", "U2"); notc["connector"] = json!("U2");-        assert!(check("t.kicad_pcb", &b, &json!({"programming": [notc]})).unwrap_err().iter().any(|e| e.contains("not a Tag-Connect footprint")));+        // the target chip is not a cable landing, and a footprint whose pads do not carry the nets fails+        let mut notc = tc("U1", "U2"); notc["connector"] = json!("U1");+        assert!(check("t.kicad_pcb", &b, &json!({"programming": [notc]})).unwrap_err().iter().any(|e| e.contains("not the target chip")));+        // the footprint's name is not evidence: a project-library pogo pad set renamed passes for U1+        let renamed = Board::from_text("t.kicad_pcb", TC_BOARD.replace("Connector:Tag-Connect_TC2030-IDC-NL_2x03_P1.27mm_Vertical", "MyLib:SWD_Pogo_6")).unwrap();+        assert!(check("t.kicad_pcb", &renamed, &json!({"programming": [tc("U1", "J7")]})).unwrap_err().iter().all(|e| e.contains("U2")), "only U2 is uncovered");+        // NRST may be left out (unwired reset); declared, it is checked+        let mut no_rst = tc("U1", "J7"); no_rst["connectorPins"].as_object_mut().unwrap().remove("NRST");+        assert!(check("t.kicad_pcb", &b, &json!({"programming": [no_rst]})).unwrap_err().iter().all(|e| e.contains("U2")));     }      #[test]
package.json+1−1
@@ -1,7 +1,7 @@ {   "slug": "adom-aiflow",   "type": "app",-  "version": "0.2.37",+  "version": "0.2.38",   "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.37",+  "version": "0.2.38",   "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
@@ -200,4 +200,4 @@ Record it as `programming` in board-release.json beside the board (or `programmi  `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. -A board with several programmable parts (four MCUs, or an MCU beside an ESP32 on its own USB) records `programming` as a list, one record per interface, each naming the part(s) it programs with `targetRef` or `targetRefs` (their part number must be its `target`); `finish` refuses a programmable part no record names. SWD over a Tag-Connect footprint (TC2030 / TC2050, a pad set made for a spring-pin cable) declares `"access": "tag-connect"` with `connector` (the Tag-Connect footprint), `targetRef`, `target` and `connectorPins` mapping `SWDIO`, `SWCLK`, `NRST` and `GND` to `{"pads": [...], "net": "..."}`; the check confirms the pads carry those nets and that the SWD nets reach the part, like the USB-cable record.+A board with several programmable parts (four MCUs, or an MCU beside an ESP32 on its own USB) records `programming` as a list, one record per interface, each naming the part(s) it programs with `targetRef` or `targetRefs` (their part number must be its `target`); `finish` refuses a programmable part no record names. SWD over a Tag-Connect footprint (TC2030 / TC2050, or any project-library pad set made for a spring-pin cable: the pads and nets are the evidence, not the footprint's name) declares `"access": "tag-connect"` with `connector` (the Tag-Connect footprint), `targetRef`, `target` and `connectorPins` mapping `SWDIO`, `SWCLK`, `GND` and (when reset is wired) `NRST` to `{"pads": [...], "net": "..."}`; the check confirms the pads carry those nets and that the SWD nets reach the part, like the USB-cable record.