mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Add graph.fabro as primary filename with graph.dot fallback
Write graph.fabro in run dirs and metadata branches. Read with graph.dot fallback for backward compatibility with existing runs. Add stack.child_workflow attribute with stack.child_dotfile fallback. No files renamed yet — fallback paths handle everything. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5d1790551
commit
f12f261ddd
7 changed files with 45 additions and 21 deletions
|
|
@ -456,7 +456,7 @@ pub async fn run_command(
|
|||
tokio::fs::create_dir_all(&run_dir).await?;
|
||||
fabro_util::run_log::activate(&run_dir.join("cli.log"))
|
||||
.context("Failed to activate per-run log")?;
|
||||
tokio::fs::write(run_dir.join("graph.dot"), &source).await?;
|
||||
tokio::fs::write(run_dir.join("graph.fabro"), &source).await?;
|
||||
tokio::fs::write(run_dir.join("run.pid"), std::process::id().to_string()).await?;
|
||||
if workflow_path.extension().is_some_and(|ext| ext == "toml") {
|
||||
if let Ok(toml_contents) = tokio::fs::read(workflow_path).await {
|
||||
|
|
@ -1576,8 +1576,10 @@ async fn run_from_branch(
|
|||
})?;
|
||||
|
||||
// Read graph DOT from metadata branch
|
||||
let source = crate::git::MetadataStore::read_graph_dot(&original_cwd, &run_id)?
|
||||
.ok_or_else(|| anyhow::anyhow!("no graph.dot found on metadata branch for run {run_id}"))?;
|
||||
let source =
|
||||
crate::git::MetadataStore::read_graph_dot(&original_cwd, &run_id)?.ok_or_else(|| {
|
||||
anyhow::anyhow!("no graph.fabro found on metadata branch for run {run_id}")
|
||||
})?;
|
||||
|
||||
// If --pipeline was also provided, use it instead (allows overriding)
|
||||
let (mut graph, diagnostics) = if let Some(ref workflow_path) = args.workflow {
|
||||
|
|
@ -1619,7 +1621,7 @@ async fn run_from_branch(
|
|||
tokio::fs::create_dir_all(&run_dir).await?;
|
||||
fabro_util::run_log::activate(&run_dir.join("cli.log"))
|
||||
.context("Failed to activate per-run log")?;
|
||||
tokio::fs::write(run_dir.join("graph.dot"), &source).await?;
|
||||
tokio::fs::write(run_dir.join("graph.fabro"), &source).await?;
|
||||
|
||||
let base_sha =
|
||||
crate::git::MetadataStore::read_manifest(&original_cwd, &run_id)?.and_then(|m| m.base_sha);
|
||||
|
|
|
|||
|
|
@ -1211,7 +1211,9 @@ impl WorkflowRunEngine {
|
|||
if let (Some(_), Some(ref repo_path)) = (&config.meta_branch, &config.host_repo_path) {
|
||||
let store = crate::git::MetadataStore::new(repo_path, &config.git_author);
|
||||
let manifest_bytes = serde_json::to_vec_pretty(&manifest).unwrap_or_default();
|
||||
let dot_source = std::fs::read(config.run_dir.join("graph.dot")).unwrap_or_default();
|
||||
let dot_source = std::fs::read(config.run_dir.join("graph.fabro"))
|
||||
.or_else(|_| std::fs::read(config.run_dir.join("graph.dot")))
|
||||
.unwrap_or_default();
|
||||
let sandbox_json = std::fs::read(config.run_dir.join("sandbox.json")).ok();
|
||||
let mut extra_files: Vec<(&str, &[u8])> = Vec::new();
|
||||
if let Some(ref data) = sandbox_json {
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ impl MetadataStore {
|
|||
bs.ensure_branch()
|
||||
.map_err(|e| git_error(format!("ensure_branch failed: {e}")))?;
|
||||
let mut entries: Vec<(&str, &[u8])> =
|
||||
vec![("manifest.json", manifest_json), ("graph.dot", graph_dot)];
|
||||
vec![("manifest.json", manifest_json), ("graph.fabro", graph_dot)];
|
||||
entries.extend_from_slice(extra_files);
|
||||
let msg = self.commit_message("init run");
|
||||
bs.write_entries(&entries, &msg)
|
||||
|
|
@ -475,8 +475,12 @@ impl MetadataStore {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the graph DOT source from the metadata branch. Returns `None` if not found.
|
||||
/// Read the graph source from the metadata branch. Tries `graph.fabro` first,
|
||||
/// then falls back to `graph.dot` for backward compatibility. Returns `None` if not found.
|
||||
pub fn read_graph_dot(repo_path: &Path, run_id: &str) -> Result<Option<String>> {
|
||||
if let Some(bytes) = Self::read_file(repo_path, run_id, "graph.fabro")? {
|
||||
return Ok(Some(String::from_utf8_lossy(&bytes).to_string()));
|
||||
}
|
||||
match Self::read_file(repo_path, run_id, "graph.dot")? {
|
||||
Some(bytes) => Ok(Some(String::from_utf8_lossy(&bytes).to_string())),
|
||||
None => Ok(None),
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ fn parse_duration_str(s: &str) -> Duration {
|
|||
}
|
||||
|
||||
/// Parse a child workflow graph from node attributes: inline `stack.child_dot_source`
|
||||
/// (no file inlining) or file path `stack.child_dotfile` (with file inlining).
|
||||
/// (no file inlining), or file path `stack.child_workflow` / `stack.child_dotfile`
|
||||
/// (with file inlining). `stack.child_workflow` is preferred; `stack.child_dotfile`
|
||||
/// is kept for backward compatibility.
|
||||
fn parse_child_graph(node: &Node) -> Result<Graph, FabroError> {
|
||||
if let Some(dot) = node
|
||||
.attrs
|
||||
|
|
@ -55,14 +57,15 @@ fn parse_child_graph(node: &Node) -> Result<Graph, FabroError> {
|
|||
}
|
||||
if let Some(path) = node
|
||||
.attrs
|
||||
.get("stack.child_dotfile")
|
||||
.get("stack.child_workflow")
|
||||
.or_else(|| node.attrs.get("stack.child_dotfile"))
|
||||
.and_then(|v| v.as_str())
|
||||
{
|
||||
let (graph, diagnostics) = prepare_from_file(std::path::Path::new(path))?;
|
||||
validation::raise_on_errors(&diagnostics)?;
|
||||
return Ok(graph);
|
||||
}
|
||||
Err(FabroError::handler("No child DOT source".to_string()))
|
||||
Err(FabroError::handler("No child workflow source".to_string()))
|
||||
}
|
||||
|
||||
/// Compute the context diff: keys that changed or were added relative to `before`.
|
||||
|
|
|
|||
|
|
@ -182,20 +182,25 @@ fn format_arc_details_section(conclusion: &Conclusion, dot_source: Option<&str>)
|
|||
fn parse_dot_summary(dot: &str) -> (String, usize, usize) {
|
||||
match crate::parser::parse(dot) {
|
||||
Ok(graph) => (
|
||||
format!("{}.dot", graph.name),
|
||||
format!("{}.fabro", graph.name),
|
||||
graph.nodes.len(),
|
||||
graph.edges.len(),
|
||||
),
|
||||
Err(_) => ("workflow.dot".to_string(), 0, 0),
|
||||
Err(_) => ("workflow.fabro".to_string(), 0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the DOT graph source from `run_dir/graph.dot`.
|
||||
/// Read the workflow graph source from `run_dir/graph.fabro` (or `graph.dot` fallback).
|
||||
fn read_dot_source(run_dir: &Path) -> Option<String> {
|
||||
let path = run_dir.join("graph.dot");
|
||||
match std::fs::read_to_string(&path) {
|
||||
let fabro_path = run_dir.join("graph.fabro");
|
||||
if let Ok(content) = std::fs::read_to_string(&fabro_path) {
|
||||
debug!(path = %fabro_path.display(), "Read workflow graph for PR body");
|
||||
return Some(content);
|
||||
}
|
||||
let dot_path = run_dir.join("graph.dot");
|
||||
match std::fs::read_to_string(&dot_path) {
|
||||
Ok(content) => {
|
||||
debug!(path = %path.display(), "Read DOT graph for PR body");
|
||||
debug!(path = %dot_path.display(), "Read workflow graph for PR body (dot fallback)");
|
||||
Some(content)
|
||||
}
|
||||
Err(_) => None,
|
||||
|
|
@ -767,11 +772,19 @@ mod tests {
|
|||
#[test]
|
||||
fn read_dot_source_found() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::fs::write(tmp.path().join("graph.dot"), "digraph test {}").unwrap();
|
||||
std::fs::write(tmp.path().join("graph.fabro"), "digraph test {}").unwrap();
|
||||
let result = read_dot_source(tmp.path());
|
||||
assert_eq!(result, Some("digraph test {}".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_dot_source_dot_fallback() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::fs::write(tmp.path().join("graph.dot"), "digraph old {}").unwrap();
|
||||
let result = read_dot_source(tmp.path());
|
||||
assert_eq!(result, Some("digraph old {}".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_dot_source_not_found() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
|
|
|||
|
|
@ -1143,8 +1143,8 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
|
|||
graph.edges.push(Edge::new("work", "exit"));
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
// Write graph.dot so init_run can read it
|
||||
std::fs::write(dir.path().join("graph.dot"), "digraph {}").unwrap();
|
||||
// Write graph.fabro so init_run can read it
|
||||
std::fs::write(dir.path().join("graph.fabro"), "digraph {}").unwrap();
|
||||
|
||||
let mut registry = HandlerRegistry::new(Box::new(FileWriterHandler));
|
||||
registry.register("start", Box::new(StartHandler));
|
||||
|
|
|
|||
|
|
@ -10865,8 +10865,8 @@ async fn git_checkpoint_host_writes_shadow_branch() {
|
|||
|
||||
// 4. Set up engine with meta_branch
|
||||
let run_dir = tempfile::tempdir().unwrap();
|
||||
// Write graph.dot so init_run can read it
|
||||
std::fs::write(run_dir.path().join("graph.dot"), "digraph {}").unwrap();
|
||||
// Write graph.fabro so init_run can read it
|
||||
std::fs::write(run_dir.path().join("graph.fabro"), "digraph {}").unwrap();
|
||||
let emitter = EventEmitter::new();
|
||||
|
||||
let env: Arc<dyn fabro_agent::Sandbox> =
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue