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
AI Flow 0.1.61: the lane comes from the brief and requirements (a Fusion run is a Fusion run from intake), and can still change while only intake is done; the timeline is titled from the brief
7 files changed
+44−20
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.1.60"+version = "0.1.61" dependencies = [ "serde", "serde_json",@@ -33,7 +33,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.1.60"+version = "0.1.61" dependencies = [ "roxmltree", "serde",@@ -42,7 +42,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-board", "serde",@@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-board", "aiflow-grid",@@ -61,7 +61,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-board", "serde",@@ -70,7 +70,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-board", "serde",@@ -79,7 +79,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-board", "aiflow-copper",@@ -89,7 +89,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.1.60"+version = "0.1.61" dependencies = [ "aiflow-board", "aiflow-grid",@@ -99,7 +99,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.1.60"+version = "0.1.61" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.1.60"+version = "0.1.61" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/main.rs+10−3@@ -3446,8 +3446,12 @@ fn main() { ok(&format!("{done} board corner(s) rounded to {rad} mm; the outline has no sharp corner"), &["Copper keeps its edge clearance from the new arcs: refill the pours (`land pours` again) if a pour followed the old corner.".into()]); } Cmd::Timeline { action, port } => {- let r = load_run(&cli);+ let mut r = load_run(&cli); let flow_full: Value = serde_json::from_str(include_str!("../../../flows/board.json")).unwrap();+ if r.data["steps"].as_object().map(|o| o.keys().all(|k| k == "intake")).unwrap_or(true) {+ let want = json!(sequence::sequence_of(&flow_full, sequence::lane(&dir, &r.data)));+ if r.data["sequence"] != want { r.data["sequence"] = want; r.save().unwrap(); }+ } let seq: Vec<String> = r.data["sequence"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_else(|| sequence::sequence_of(&flow_full, sequence::lane(&dir, &r.data))); let tl = sequence::timeline(&dir, &r.data, &flow_full, &seq, &r.step_report()); sequence::write_timeline(&dir, &tl, TIMELINE_PAGE);@@ -3937,8 +3941,11 @@ fn main() { // the required steps, in order: a run that starts now carries its lane's sequence, and no step // in it can be skipped (sequence.rs) let flow_full: Value = serde_json::from_str(include_str!("../../../flows/board.json")).unwrap();- if r.data["sequence"].is_null() && r.data["steps"].as_object().map(|o| o.keys().all(|k| k == "intake")).unwrap_or(true) {- r.data["sequence"] = json!(sequence::sequence_of(&flow_full, sequence::lane(&dir, &r.data)));+ // the lane can still change while only intake is done (the run learns it is a Fusion board from the+ // brief or the requirements written during intake)+ if r.data["steps"].as_object().map(|o| o.keys().all(|k| k == "intake")).unwrap_or(true) {+ let want = json!(sequence::sequence_of(&flow_full, sequence::lane(&dir, &r.data)));+ if r.data["sequence"] != want { r.data["sequence"] = want; } } let seq: Vec<String> = r.data["sequence"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default(); if !seq.is_empty() && !*back {
crates/adom-aiflow/src/sequence.rs+20−2@@ -33,10 +33,28 @@ pub struct StepState { /// Which lane a run is on: a Fusion project or run, else KiCad. pub fn lane(dir: &Path, data: &Value) -> &'static str {- if dir.join("fusion").join("project.json").is_file() || data["engine"].as_str().map(|e| e.to_ascii_lowercase().contains("fusion")).unwrap_or(false)+ // the human's own words decide it before any board exists: a brief or requirements that name Fusion+ let said_fusion = ["brief", "requirements"].iter().any(|k| {+ data["intake"][k].as_str().or(data[*k].as_str()).and_then(|p| std::fs::read_to_string(p).ok())+ .map(|t| t.to_ascii_lowercase().contains("fusion")).unwrap_or(false)+ });+ if said_fusion || dir.join("fusion").join("project.json").is_file() || data["engine"].as_str().map(|e| e.to_ascii_lowercase().contains("fusion")).unwrap_or(false) || data["remoteBoard"].as_str().map(|b| b.starts_with("fusion:")).unwrap_or(false) { "fusion" } else { "kicad" } } +/// The run's name for the timeline: the requirements' title, else the brief's first line.+pub fn title(data: &Value) -> String {+ let req = data["intake"]["requirements"].as_str().or(data["requirements"].as_str()).and_then(|p| std::fs::read_to_string(p).ok()).and_then(|t| serde_json::from_str::<Value>(&t).ok());+ if let Some(t) = req.as_ref().and_then(|v| v["title"].as_str().or(v["name"].as_str()).or(v["design"].as_str())) { return t.to_string(); }+ if let Some(b) = data["intake"]["brief"].as_str().and_then(|p| std::fs::read_to_string(p).ok()) {+ if let Some(l) = b.lines().map(str::trim).find(|l| !l.is_empty() && !l.starts_with('{')) {+ let l = l.trim_start_matches('#').trim();+ return if l.chars().count() > 70 { format!("{}...", l.chars().take(67).collect::<String>()) } else { l.to_string() };+ }+ }+ data["title"].as_str().unwrap_or("adom-aiflow").to_string()+}+ pub fn sequence_of(flow: &Value, lane: &str) -> Vec<String> { flow["sequence"][lane].as_array().map(|a| a.iter().filter_map(|s| s.as_str().map(str::to_string)).collect()).unwrap_or_default() }@@ -152,7 +170,7 @@ pub fn timeline(dir: &Path, data: &Value, flow: &Value, seq: &[String], minutes: } let idx = st.iter().position(|s| s.name == current).map(|i| i + 1).unwrap_or(st.iter().filter(|s| s.status == Status::Done).count() + if current.is_empty() && !st.is_empty() { 1 } else { 0 }).min(st.len()); json!({- "title": data["title"].as_str().or(data["requirements"]["title"].as_str()).unwrap_or("adom-aiflow"),+ "title": title(data), "engine": if lane(dir, data) == "fusion" { "Fusion" } else { "KiCad" }, "finish": flow["finishLine"][lane(dir, data)].as_str().unwrap_or("Gerbers, BOM and CPL exported"), "updated": aiflow_run::now(),
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.60",+ "version": "0.1.61", "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+2−3@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.60",+ "version": "0.1.61", "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.",@@ -89,7 +89,6 @@ "email": "[email protected]" }, "confirmed_user_skills": [- "aiflow-simulate",- "aiflow-test-plan"+ "aiflow-simulate" ] }