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
0.1.40: per-run clips pages; a later run links its own page instead of overwriting the first run's
7 files changed
+45−18
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.1.39"+version = "0.1.40" dependencies = [ "serde", "serde_json",@@ -33,7 +33,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.1.39"+version = "0.1.40" dependencies = [ "serde", "serde_json",@@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-board", "serde",@@ -50,7 +50,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-board", "aiflow-grid",@@ -60,7 +60,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-board", "serde",@@ -69,7 +69,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-board", "serde",@@ -78,7 +78,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-board", "aiflow-copper",@@ -88,7 +88,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.1.39"+version = "0.1.40" dependencies = [ "aiflow-board", "aiflow-grid",@@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.1.39"+version = "0.1.40" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.1.39"+version = "0.1.40" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/main.rs+29−5@@ -232,20 +232,44 @@ fn publish_clips(r: &Run, dir: &Path, page: &str, push: bool) -> Result<(String, let (md, media) = preboard::clips_page(&r.data, dir, ¬es, &now()); let stage = dir.join("clips-page"); std::fs::create_dir_all(stage.join("docs").join("clips")).map_err(|e| format!("{}: {e}", stage.display()))?;- std::fs::write(stage.join("docs").join("clips.md"), &md).map_err(|e| e.to_string())?;+ // docs/clips.md belongs to the first run that published it (a hidden marker says which). A later+ // run on the same page writes docs/clips-<run>.md and adds one link to it in docs/clips.md,+ // so a second run never overwrites the first run's clips.+ let id = r.data["reportId"].as_str().map(str::to_string).unwrap_or_else(|| report_id(r));+ let marker = format!("<!-- aiflow-clips-run: {id} -->");+ let remote = std::process::Command::new("adom-wiki").args(["repo", "show", page, "docs/clips.md"]).output().ok()+ .filter(|o| o.status.success()).map(|o| String::from_utf8_lossy(&o.stdout).to_string()).filter(|t| t.trim_start().starts_with('#') || t.contains("aiflow-clips-run"));+ let owner = match &remote { None => true, Some(t) => t.contains(&marker) };+ let doc = if owner { "docs/clips.md".to_string() } else { format!("docs/clips-{id}.md") };+ let md = if owner { format!("{marker}\n{md}") } else { format!("{marker}\n{}", md.replacen("# Build clips", &format!("# Build clips: run {id}"), 1)) };+ std::fs::write(stage.join(&doc), &md).map_err(|e| e.to_string())?;+ let mut index_update: Option<String> = None;+ if let (false, Some(t)) = (owner, &remote) {+ let link = format!("clips-{id}.md");+ if !t.contains(&link) {+ let line = format!("Clips of a later run on this page: [run {id}]({link}).");+ let mut lines: Vec<&str> = t.lines().collect();+ let at = lines.iter().position(|l| l.starts_with("# ")).map(|i| i + 1).unwrap_or(0);+ lines.insert(at, ""); lines.insert(at + 1, &line);+ let updated = lines.join("\n") + "\n";+ std::fs::write(stage.join("docs").join("clips.md"), &updated).map_err(|e| e.to_string())?;+ index_update = Some(updated);+ }+ } for (src, rel) in &media { let dest = stage.join(rel); let same = std::fs::metadata(&dest).ok().zip(std::fs::metadata(src).ok()).map(|(a, b)| a.len() == b.len()).unwrap_or(false); if !same { std::fs::copy(src, &dest).map_err(|e| format!("copy {}: {e}", src.display()))?; } } let clips = md.matches("\n### Clip ").count();- let mut files: Vec<String> = vec!["docs/clips.md".into()];+ let mut files: Vec<String> = vec![doc.clone()];+ if index_update.is_some() { files.push("docs/clips.md".into()); } files.extend(media.iter().map(|(_, rel)| rel.clone())); let banned = std::env::var("AIFLOW_BANNED_RE").ok(); let scrubbed = preboard::scrub(&md, banned.as_deref());- let url = format!("https://wiki.adom.inc/{page}/files/docs/clips.md");+ let url = format!("https://wiki.adom.inc/{page}/files/{doc}"); if !push {- return Ok((format!("docs/clips.md rebuilt with {clips} clip(s) and {} media file(s) in {} (dry run, nothing pushed; {}); {url} after --push", media.len(), stage.display(), match &scrubbed { Ok(()) => "the scrub passes".to_string(), Err(e) => format!("a push would be refused: {e}") }), files));+ return Ok((format!("{doc} rebuilt with {clips} clip(s) and {} media file(s) in {} (dry run, nothing pushed; {}); {url} after --push", media.len(), stage.display(), match &scrubbed { Ok(()) => "the scrub passes".to_string(), Err(e) => format!("a push would be refused: {e}") }), files)); } scrubbed.map_err(|e| format!("not pushed: {e}"))?; // only what changed since the last push goes up (docs/clips.md always), in batches the gateway takes@@ -255,7 +279,7 @@ fn publish_clips(r: &Run, dir: &Path, page: &str, push: bool) -> Result<(String, for f in &files { let p = stage.join(f); let sum = library_review::hash(&p).unwrap_or_default();- if f == "docs/clips.md" || cache.get(f).and_then(|v| v.as_str()) != Some(sum.as_str()) { todo.push((f.clone(), std::fs::metadata(&p).map(|m| m.len()).unwrap_or(0), sum)); }+ if f == &doc || f == "docs/clips.md" || cache.get(f).and_then(|v| v.as_str()) != Some(sum.as_str()) { todo.push((f.clone(), std::fs::metadata(&p).map(|m| m.len()).unwrap_or(0), sum)); } } let mut batches: Vec<Vec<&(String, u64, String)>> = vec![Vec::new()]; let mut acc = 0u64;
docs/release-0.1.40.mdadded+3@@ -0,0 +1,3 @@+# AI Flow 0.1.40++- **A second run can no longer overwrite the first run's clips page.** `docs/clips.md` belonged to whichever run published last, so a rework run on the same wiki page (its `report --push` or any clip stop) replaced the first run's clips. The first run to publish `docs/clips.md` now owns it (a hidden `<!-- aiflow-clips-run: <run> -->` marker); a later run writes `docs/clips-<run>.md` and adds one link to it in `docs/clips.md`, leaving the rest of that page as it was.
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.39",+ "version": "0.1.40", "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.39",+ "version": "0.1.40", "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.",