mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
parent
a523a883fb
commit
0fbf4dc46f
5 changed files with 339 additions and 34 deletions
File diff suppressed because one or more lines are too long
272
nodes/simplify_gpt/diff.patch
Normal file
272
nodes/simplify_gpt/diff.patch
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
diff --git a/lib/crates/fabro-cli/src/commands/run.rs b/lib/crates/fabro-cli/src/commands/run.rs
|
||||||
|
index dfe34fa9..d41bef0e 100644
|
||||||
|
--- a/lib/crates/fabro-cli/src/commands/run.rs
|
||||||
|
+++ b/lib/crates/fabro-cli/src/commands/run.rs
|
||||||
|
@@ -8,8 +8,7 @@ use anyhow::{bail, Context};
|
||||||
|
use chrono::{Local, Utc};
|
||||||
|
use clap::{Args, ValueEnum};
|
||||||
|
use fabro_agent::{
|
||||||
|
- DockerSandbox, DockerSandboxConfig, LocalSandbox, Sandbox, WorktreeConfig, WorktreeEvent,
|
||||||
|
- WorktreeEventCallback, WorktreeSandbox,
|
||||||
|
+ DockerSandbox, DockerSandboxConfig, LocalSandbox, Sandbox, WorktreeConfig, WorktreeSandbox,
|
||||||
|
};
|
||||||
|
use fabro_config::run::{RunDefaults, WorkflowRunConfig};
|
||||||
|
use fabro_config::{project as project_config, run as run_config, sandbox as sandbox_config};
|
||||||
|
@@ -423,25 +422,6 @@ struct CostAccumulator {
|
||||||
|
has_pricing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
-/// Build a callback that forwards [`WorktreeEvent`]s as workflow run events on `emitter`.
|
||||||
|
-fn worktree_event_callback(emitter: Arc<EventEmitter>) -> WorktreeEventCallback {
|
||||||
|
- Arc::new(move |event| match event {
|
||||||
|
- WorktreeEvent::BranchCreated { branch, sha } => {
|
||||||
|
- emitter.emit(&fabro_workflows::event::WorkflowRunEvent::GitBranch { branch, sha });
|
||||||
|
- }
|
||||||
|
- WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||||
|
- emitter
|
||||||
|
- .emit(&fabro_workflows::event::WorkflowRunEvent::GitWorktreeAdd { path, branch });
|
||||||
|
- }
|
||||||
|
- WorktreeEvent::WorktreeRemoved { path } => {
|
||||||
|
- emitter.emit(&fabro_workflows::event::WorkflowRunEvent::GitWorktreeRemove { path });
|
||||||
|
- }
|
||||||
|
- WorktreeEvent::Reset { sha } => {
|
||||||
|
- emitter.emit(&fabro_workflows::event::WorkflowRunEvent::GitReset { sha });
|
||||||
|
- }
|
||||||
|
- })
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
/// Create a [`LocalSandbox`] wired to emit [`WorkflowRunEvent::Sandbox`] events.
|
||||||
|
fn local_sandbox_with_callback(cwd: PathBuf, emitter: Arc<EventEmitter>) -> Arc<dyn Sandbox> {
|
||||||
|
let mut env = LocalSandbox::new(cwd);
|
||||||
|
@@ -1168,7 +1148,7 @@ pub async fn run_command(
|
||||||
|
skip_branch_creation: false,
|
||||||
|
};
|
||||||
|
let mut wt_sandbox = WorktreeSandbox::new(inner, wt_config);
|
||||||
|
- wt_sandbox.set_event_callback(worktree_event_callback(Arc::clone(&emitter)));
|
||||||
|
+ wt_sandbox.set_event_callback(Arc::clone(&emitter).worktree_callback());
|
||||||
|
|
||||||
|
match wt_sandbox.initialize().await {
|
||||||
|
Ok(()) => {
|
||||||
|
@@ -1866,7 +1846,7 @@ async fn run_from_branch(
|
||||||
|
skip_branch_creation: true, // branch already exists on resume
|
||||||
|
};
|
||||||
|
let mut wt_sandbox = WorktreeSandbox::new(inner, wt_config);
|
||||||
|
- wt_sandbox.set_event_callback(worktree_event_callback(Arc::clone(&emitter)));
|
||||||
|
+ wt_sandbox.set_event_callback(Arc::clone(&emitter).worktree_callback());
|
||||||
|
|
||||||
|
wt_sandbox.initialize().await.map_err(|e| {
|
||||||
|
anyhow::anyhow!("failed to attach worktree to {run_branch}: {e}")
|
||||||
|
diff --git a/lib/crates/fabro-sandbox/src/worktree.rs b/lib/crates/fabro-sandbox/src/worktree.rs
|
||||||
|
index 3283ae14..d9806166 100644
|
||||||
|
--- a/lib/crates/fabro-sandbox/src/worktree.rs
|
||||||
|
+++ b/lib/crates/fabro-sandbox/src/worktree.rs
|
||||||
|
@@ -18,7 +18,6 @@ pub enum WorktreeEvent {
|
||||||
|
BranchCreated { branch: String, sha: String },
|
||||||
|
WorktreeAdded { path: String, branch: String },
|
||||||
|
WorktreeRemoved { path: String },
|
||||||
|
- Reset { sha: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback type for worktree lifecycle events.
|
||||||
|
@@ -143,30 +142,6 @@ impl Sandbox for WorktreeSandbox {
|
||||||
|
branch: self.config.branch_name.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
- if !self.config.skip_branch_creation {
|
||||||
|
- let reset_cmd = format!("{GIT} reset --hard {sha}");
|
||||||
|
- let result = self
|
||||||
|
- .inner
|
||||||
|
- .exec_command(
|
||||||
|
- &reset_cmd,
|
||||||
|
- 30_000,
|
||||||
|
- Some(&self.config.worktree_path),
|
||||||
|
- None,
|
||||||
|
- None,
|
||||||
|
- )
|
||||||
|
- .await?;
|
||||||
|
- if result.exit_code != 0 {
|
||||||
|
- return Err(format!(
|
||||||
|
- "git reset --hard failed (exit {}): {}",
|
||||||
|
- result.exit_code,
|
||||||
|
- result.stderr.trim()
|
||||||
|
- ));
|
||||||
|
- }
|
||||||
|
- self.emit(WorktreeEvent::Reset {
|
||||||
|
- sha: self.config.base_sha.clone(),
|
||||||
|
- });
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -359,8 +334,8 @@ mod tests {
|
||||||
|
wt.initialize().await.unwrap();
|
||||||
|
|
||||||
|
let cmds = mock.captured_commands.lock().unwrap().clone();
|
||||||
|
- // branch --force, worktree remove (best-effort), worktree add, reset --hard
|
||||||
|
- assert_eq!(cmds.len(), 4, "expected 4 git commands, got: {cmds:?}");
|
||||||
|
+ // branch --force, worktree remove (best-effort), worktree add
|
||||||
|
+ assert_eq!(cmds.len(), 3, "expected 3 git commands, got: {cmds:?}");
|
||||||
|
assert!(cmds[0].contains("branch --force"), "cmd[0]: {}", cmds[0]);
|
||||||
|
assert!(
|
||||||
|
cmds[1].contains("worktree remove --force"),
|
||||||
|
@@ -368,11 +343,10 @@ mod tests {
|
||||||
|
cmds[1]
|
||||||
|
);
|
||||||
|
assert!(cmds[2].contains("worktree add"), "cmd[2]: {}", cmds[2]);
|
||||||
|
- assert!(cmds[3].contains("reset --hard"), "cmd[3]: {}", cmds[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
- async fn initialize_emits_branch_worktree_reset_events() {
|
||||||
|
+ async fn initialize_emits_branch_and_worktree_events() {
|
||||||
|
let (inner, _mock) = make_mock();
|
||||||
|
let mut wt = WorktreeSandbox::new(inner, make_config("/tmp/wt"));
|
||||||
|
|
||||||
|
@@ -383,7 +357,6 @@ mod tests {
|
||||||
|
WorktreeEvent::BranchCreated { .. } => "BranchCreated",
|
||||||
|
WorktreeEvent::WorktreeAdded { .. } => "WorktreeAdded",
|
||||||
|
WorktreeEvent::WorktreeRemoved { .. } => "WorktreeRemoved",
|
||||||
|
- WorktreeEvent::Reset { .. } => "Reset",
|
||||||
|
};
|
||||||
|
events_clone.lock().unwrap().push(label.to_string());
|
||||||
|
}));
|
||||||
|
@@ -391,7 +364,7 @@ mod tests {
|
||||||
|
wt.initialize().await.unwrap();
|
||||||
|
|
||||||
|
let captured = events.lock().unwrap();
|
||||||
|
- assert_eq!(*captured, vec!["BranchCreated", "WorktreeAdded", "Reset"]);
|
||||||
|
+ assert_eq!(*captured, vec!["BranchCreated", "WorktreeAdded"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
@@ -416,26 +389,6 @@ mod tests {
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
- #[tokio::test]
|
||||||
|
- async fn initialize_reset_uses_worktree_path_as_working_dir() {
|
||||||
|
- let (inner, mock) = make_mock();
|
||||||
|
- let wt = WorktreeSandbox::new(inner, make_config("/tmp/wt"));
|
||||||
|
-
|
||||||
|
- wt.initialize().await.unwrap();
|
||||||
|
-
|
||||||
|
- let wdirs = mock.captured_working_dirs.lock().unwrap().clone();
|
||||||
|
- // reset command is at index 3, should use worktree path
|
||||||
|
- assert_eq!(
|
||||||
|
- wdirs[3],
|
||||||
|
- Some("/tmp/wt".to_string()),
|
||||||
|
- "reset --hard should run in worktree dir"
|
||||||
|
- );
|
||||||
|
- // branch, remove, add commands use None (inner's default)
|
||||||
|
- assert_eq!(wdirs[0], None, "branch command should use inner default");
|
||||||
|
- assert_eq!(wdirs[1], None, "worktree remove should use inner default");
|
||||||
|
- assert_eq!(wdirs[2], None, "worktree add should use inner default");
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// initialize() — skip_branch_creation = true
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
@@ -470,7 +423,6 @@ mod tests {
|
||||||
|
WorktreeEvent::BranchCreated { .. } => "BranchCreated",
|
||||||
|
WorktreeEvent::WorktreeAdded { .. } => "WorktreeAdded",
|
||||||
|
WorktreeEvent::WorktreeRemoved { .. } => "WorktreeRemoved",
|
||||||
|
- WorktreeEvent::Reset { .. } => "Reset",
|
||||||
|
};
|
||||||
|
events_clone.lock().unwrap().push(label.to_string());
|
||||||
|
}));
|
||||||
|
diff --git a/lib/crates/fabro-workflows/src/event.rs b/lib/crates/fabro-workflows/src/event.rs
|
||||||
|
index 227f18fa..03c7d3bd 100644
|
||||||
|
--- a/lib/crates/fabro-workflows/src/event.rs
|
||||||
|
+++ b/lib/crates/fabro-workflows/src/event.rs
|
||||||
|
@@ -1,9 +1,10 @@
|
||||||
|
use std::sync::atomic::{AtomicI64, Ordering};
|
||||||
|
+use std::sync::Arc;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::outcome::StageUsage;
|
||||||
|
-use fabro_agent::{AgentEvent, SandboxEvent};
|
||||||
|
+use fabro_agent::{AgentEvent, SandboxEvent, WorktreeEvent, WorktreeEventCallback};
|
||||||
|
|
||||||
|
/// Events emitted during workflow run execution for observability.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@@ -1070,6 +1071,22 @@ impl EventEmitter {
|
||||||
|
pub fn touch(&self) {
|
||||||
|
self.last_event_at.store(epoch_millis(), Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ /// Build a [`WorktreeEventCallback`] that forwards worktree lifecycle events as
|
||||||
|
+ /// [`WorkflowRunEvent`]s on this emitter.
|
||||||
|
+ pub fn worktree_callback(self: Arc<Self>) -> WorktreeEventCallback {
|
||||||
|
+ Arc::new(move |event| match event {
|
||||||
|
+ WorktreeEvent::BranchCreated { branch, sha } => {
|
||||||
|
+ self.emit(&WorkflowRunEvent::GitBranch { branch, sha });
|
||||||
|
+ }
|
||||||
|
+ WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||||
|
+ self.emit(&WorkflowRunEvent::GitWorktreeAdd { path, branch });
|
||||||
|
+ }
|
||||||
|
+ WorktreeEvent::WorktreeRemoved { path } => {
|
||||||
|
+ self.emit(&WorkflowRunEvent::GitWorktreeRemove { path });
|
||||||
|
+ }
|
||||||
|
+ })
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
diff --git a/lib/crates/fabro-workflows/src/handler/parallel.rs b/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||||
|
index 5ff62054..63ca994f 100644
|
||||||
|
--- a/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||||
|
+++ b/lib/crates/fabro-workflows/src/handler/parallel.rs
|
||||||
|
@@ -3,14 +3,14 @@ use std::sync::Arc;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
-use fabro_agent::{Sandbox, WorktreeConfig, WorktreeEvent, WorktreeEventCallback, WorktreeSandbox};
|
||||||
|
+use fabro_agent::{Sandbox, WorktreeConfig, WorktreeSandbox};
|
||||||
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
|
use crate::context::keys;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::engine::set_hook_node;
|
||||||
|
use crate::error::FabroError;
|
||||||
|
-use crate::event::{EventEmitter, WorkflowRunEvent};
|
||||||
|
+use crate::event::WorkflowRunEvent;
|
||||||
|
use crate::millis_u64;
|
||||||
|
use crate::outcome::{Outcome, StageStatus};
|
||||||
|
use fabro_graphviz::graph::{Graph, Node};
|
||||||
|
@@ -91,24 +91,6 @@ fn parse_error_policy(raw: &str) -> ErrorPolicy {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-/// Bridge a [`WorktreeEvent`] to the corresponding workflow run event on `emitter`.
|
||||||
|
-fn worktree_event_callback(emitter: Arc<EventEmitter>) -> WorktreeEventCallback {
|
||||||
|
- Arc::new(move |event| match event {
|
||||||
|
- WorktreeEvent::BranchCreated { branch, sha } => {
|
||||||
|
- emitter.emit(&WorkflowRunEvent::GitBranch { branch, sha });
|
||||||
|
- }
|
||||||
|
- WorktreeEvent::WorktreeAdded { path, branch } => {
|
||||||
|
- emitter.emit(&WorkflowRunEvent::GitWorktreeAdd { path, branch });
|
||||||
|
- }
|
||||||
|
- WorktreeEvent::WorktreeRemoved { path } => {
|
||||||
|
- emitter.emit(&WorkflowRunEvent::GitWorktreeRemove { path });
|
||||||
|
- }
|
||||||
|
- WorktreeEvent::Reset { sha } => {
|
||||||
|
- emitter.emit(&WorkflowRunEvent::GitReset { sha });
|
||||||
|
- }
|
||||||
|
- })
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
struct BranchResult {
|
||||||
|
id: String,
|
||||||
|
outcome: Outcome,
|
||||||
|
@@ -313,8 +295,7 @@ impl Handler for ParallelHandler {
|
||||||
|
skip_branch_creation: false,
|
||||||
|
};
|
||||||
|
let mut wt_sandbox = WorktreeSandbox::new(Arc::clone(&services.sandbox), wt_config);
|
||||||
|
- wt_sandbox
|
||||||
|
- .set_event_callback(worktree_event_callback(Arc::clone(&services.emitter)));
|
||||||
|
+ wt_sandbox.set_event_callback(Arc::clone(&services.emitter).worktree_callback());
|
||||||
|
wt_sandbox
|
||||||
|
.initialize()
|
||||||
|
.await
|
||||||
5
nodes/verify/script_invocation.json
Normal file
5
nodes/verify/script_invocation.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"command": "cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1",
|
||||||
|
"language": "shell",
|
||||||
|
"timeout_ms": null
|
||||||
|
}
|
||||||
5
nodes/verify/script_timing.json
Normal file
5
nodes/verify/script_timing.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"duration_ms": 3878,
|
||||||
|
"exit_code": 100,
|
||||||
|
"timed_out": false
|
||||||
|
}
|
||||||
6
nodes/verify/status.json
Normal file
6
nodes/verify/status.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue