← 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

John Lauer ·f7824c0744 · ·parent d3e1a4b
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"   ] }\ No newline at end of file