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
Thermal counts the saved board's vias on each tab net, not only the routing plan's (0.1.96)
8 files changed
+42−15
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -25,7 +25,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "serde",@@ -35,7 +35,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.1.95"+version = "0.1.96" dependencies = [ "roxmltree", "serde",@@ -44,7 +44,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "serde",@@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "aiflow-grid",@@ -63,7 +63,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "serde",@@ -72,7 +72,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "serde",@@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "aiflow-copper",@@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.1.95"+version = "0.1.96" dependencies = [ "aiflow-board", "aiflow-grid",@@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.1.95"+version = "0.1.96" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.1.95"+version = "0.1.96" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
SKILL.md+1@@ -288,6 +288,7 @@ The gates read the board and the tools' own output, so a file written to look li - **3D models are the parts.** A renamed or resized laser-etch proxy box is refused by `models` and `finish`. Use the wiki component page's model, the vendor STEP, or a model built from the datasheet. - **Silkscreen is printed.** `finish` refuses a board with fewer than 0.5 visible silkscreen labels per footprint (finished boards carry about 1 to 3.5): references, values and contact labels go on the silkscreen. - `step <name> --back` only returns to an earlier step.+- **Thermal counts the board's vias (0.1.96).** `analyze thermal` counts the vias on each tab net in the saved board as well as the routing plan, so thermal vias landed after routing count once the board is saved. - **Budgets are fixed once placement begins (0.1.95).** The spec's budgets and physical models (`loads`, `hot` watts and `tabNet`, `hotMaxRiseC`/`maxRiseC`, `switchNodeMaxLengthMm`, `wideNets`/`midNets`, `fab` and copper weights, via/drill power, package cooling) freeze at placement in `<run>/spec-budgets.json`. When a check fails, change the board: more copper, vias, a tighter placement. Every gate refuses a spec whose budgets moved since. Layout keys (`pours`, `thermalVias`, fixed coordinates, `planeUnder`) stay editable. If a budget really is wrong (a misread datasheet value), `adom-aiflow spec-amend --why "<datasheet page and what was misread>"` records the change; amendments are listed in the run ledger and the finish summary. ## Component coverage before placement
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/main.rs+1−1@@ -3237,7 +3237,7 @@ fn main() { let zs = if zsp.is_file() { Some(read_json(&zsp.display().to_string())) } else { None }; let name = format!("analyze-{what}"); r.stage_start(&name, Some("binary"), None);- let (heuristic_passed, res) = match what.as_str() { "current" => aiflow_analyze::analyze_current_on_board(&plan_v, &spec, zs.as_ref(), Some(&pad_layers_of(&board_now)), &board_now), "thermal" => aiflow_analyze::analyze_thermal(&plan_v, &spec, zs.as_ref()), other => err(&format!("analyze {other}: use current or thermal"), &[]) };+ let (heuristic_passed, res) = match what.as_str() { "current" => aiflow_analyze::analyze_current_on_board(&plan_v, &spec, zs.as_ref(), Some(&pad_layers_of(&board_now)), &board_now), "thermal" => aiflow_analyze::analyze_thermal_on_board(&plan_v, &spec, zs.as_ref(), &board_now), other => err(&format!("analyze {other}: use current or thermal"), &[]) }; let mut fields_file=fields.clone().unwrap_or_else(||dir.join("fields/fields.json")); // provenance: a fields.json is evidence only when it is the output of a solve aiflow ran or watched; // any other file (copied, edited, or written to look like one) is solved again here, same inputs
crates/aiflow-analyze/src/lib.rs+27−1@@ -153,6 +153,18 @@ pub fn analyze_current_on_board(plan: &Value, spec: &Value, zone_state: Option<& /// spec.hot: {ref: {watts, tabNet, maxThetaCPerW?}}. 0.1: copper presence and vias at the tab. pub fn analyze_thermal(plan: &Value, spec: &Value, zone_state: Option<&Value>) -> (bool, Vec<Value>) {+ thermal_with_vias(plan, spec, zone_state, None)+}++/// `analyze_thermal` counting the vias on the saved board too: thermal vias added after routing+/// (thermal-vias, live via landing) are on the board, not in the routing plan.+pub fn analyze_thermal_on_board(plan: &Value, spec: &Value, zone_state: Option<&Value>, board: &aiflow_board::Board) -> (bool, Vec<Value>) {+ let mut on_board: BTreeMap<String, usize> = BTreeMap::new();+ for v in board.vias() { *on_board.entry(v.net).or_default() += 1; }+ thermal_with_vias(plan, spec, zone_state, Some(&on_board))+}++fn thermal_with_vias(plan: &Value, spec: &Value, zone_state: Option<&Value>, board_vias: Option<&BTreeMap<String, usize>>) -> (bool, Vec<Value>) { if let Err(error) = package_cooling::validate(spec) { return (false, vec![json!({"ref":"packageCooling","status":"fail","hint":error})]); }@@ -172,7 +184,7 @@ pub fn analyze_thermal(plan: &Value, spec: &Value, zone_state: Option<&Value>) - let zs = pours.get(&net).cloned().unwrap_or_default(); let area = (fill_area(&zs) * 10.0).round() / 10.0; let layers = layers_of(&zs);- let vias = widths.get(&net).map(|w| w.vias).unwrap_or(0);+ let vias = widths.get(&net).map(|w| w.vias).unwrap_or(0).max(board_vias.and_then(|m| m.get(&net)).copied().unwrap_or(0)); let theta = 40.0 / (area / 645.16).max(0.05); // limits: a theta ceiling (maxThetaCPerW, per part or spec-wide) and/or a rise budget // (maxRiseC per part, hotMaxRiseC spec-wide); with neither, 60 C/W@@ -222,6 +234,20 @@ mod tests { #[cfg(test)] mod regression_tests { use super::*;+ #[test] fn thermal_counts_vias_landed_after_routing() {+ // run 2026-10-15: SW poured on both layers, two vias landed after routing; the plan had none+ let plan=json!({"nets":[{"net":"SW","width":0.8,"paths":[[{"x":1.0,"y":1.0},{"x":2.0,"y":1.0}]]}]});+ let spec=json!({"hot":{"L1":{"watts":0.066,"tabNet":"SW"}},"hotMaxRiseC":50});+ let zs=json!({"zones":[{"type":"copper","net":"SW","layers":["F.Cu"],"fills":[{"areaMm2":30.0}]},{"type":"copper","net":"SW","layers":["B.Cu"],"fills":[{"areaMm2":35.0}]}]});+ let (pass,r)=analyze_thermal(&plan,&spec,Some(&zs));+ assert!(!pass); assert_eq!(r[0]["viasOnNet"],0);+ let pcb=r#"(kicad_pcb (version 20241229) (generator "pcbnew") (net 0 "") (net 1 "SW") (gr_rect (start 100 80) (end 130 100) (layer "Edge.Cuts"))+ (via (at 116 90.2) (size 0.6) (drill 0.3) (layers "F.Cu" "B.Cu") (net 1))+ (via (at 116 91.2) (size 0.6) (drill 0.3) (layers "F.Cu" "B.Cu") (net 1)))"#;+ let b=aiflow_board::Board::from_text("t.kicad_pcb",pcb.to_string()).unwrap();+ let (pass,r)=analyze_thermal_on_board(&plan,&spec,Some(&zs),&b);+ assert_eq!(r[0]["viasOnNet"],2); assert!(pass,"{r:?}");+ } #[test] fn wide_series_segment_cannot_hide_a_narrow_bottleneck() { let plan=json!({"nets":[{"net":"POWER","width":0.25},{"net":"POWER","width":40.0}]}); let spec=json!({"loads":{"POWER":{"amps":20.0,"maxRiseC":10.0}},"copperUm":35.0});
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.95",+ "version": "0.1.96", "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 today; Altium, Fusion and 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.1.95",+ "version": "0.1.96", "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 today; Altium, Fusion and 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.",