diff --git a/checkpoint.json b/checkpoint.json new file mode 100644 index 000000000..8eebf6bb5 --- /dev/null +++ b/checkpoint.json @@ -0,0 +1,52 @@ +{ + "timestamp": "2026-03-20T01:05:04.381227Z", + "current_node": "toolchain", + "completed_nodes": [ + "start", + "toolchain" + ], + "node_retries": { + "start": 1, + "toolchain": 1 + }, + "context_values": { + "failure_class": "", + "internal.run_id": "01KM4CCACXBP0M7KEEWK784ZD9", + "command.output": "cargo 1.94.0 (85eff7c80 2026-01-15)\n", + "internal.thread_id": "start", + "thread.start.current_node": "toolchain", + "outcome": "success", + "internal.retry_count.toolchain": 1, + "internal.fidelity": "compact", + "command.stderr": "", + "graph.model_stylesheet": "\n * { backend: api; model: claude-opus-4-6;}\n ", + "internal.node_visit_count": 1, + "failure_signature": "", + "internal.retry_count.start": 1, + "graph.rankdir": "LR", + "graph.goal": "# Unified WorktreeSandbox\n\n## Context\n\nWorktree management is currently split across two locations with duplicated logic:\n\n1. **Parallel branches** (`parallel.rs`): Inline git commands in `ParallelHandler::execute()`, a thin `WorktreeSandbox` decorator, and separate local/remote code paths\n2. **Top-level CLI run** (`run.rs`): A `setup_worktree()` function using synchronous git helpers, with the worktree path fed into a plain `LocalSandbox`\n\nThe goal is a single `WorktreeSandbox` type that wraps any `Arc`, manages the worktree lifecycle in `initialize()`/`cleanup()`, and eliminates the local/remote branching.\n\n## Plan\n\n### Step 1: Create `WorktreeSandbox` in fabro-sandbox\n\n**New file:** `lib/crates/fabro-sandbox/src/worktree.rs`\n\nDefine:\n\n```rust\npub enum WorktreeEvent {\n BranchCreated { branch: String, sha: String },\n WorktreeAdded { path: String, branch: String },\n WorktreeRemoved { path: String },\n Reset { sha: String },\n}\n\npub type WorktreeEventCallback = Arc;\n\npub struct WorktreeConfig {\n pub branch_name: String,\n pub base_sha: String,\n pub worktree_path: String,\n /// Skip branch creation and reset (for resume, where branch already exists).\n pub skip_branch_creation: bool,\n}\n\npub struct WorktreeSandbox {\n inner: Arc,\n config: WorktreeConfig,\n event_callback: Option,\n}\n```\n\n**Constructor + getters:** `new(inner, config)`, `set_event_callback()`, `branch_name()`, `base_sha()`, `worktree_path()`\n\n**`initialize()`:**\n1. If `!skip_branch_creation`: `git branch --force {branch_name} {base_sha}` via `inner.exec_command()`, emit `BranchCreated`\n2. `git worktree remove --force {path}` (best-effort), then `git worktree add {path} {branch}`, emit `WorktreeAdded`\n3. If `!skip_branch_creation`: `git reset --hard {base_sha}` in worktree dir, emit `Reset`\n\nDoes NOT call `inner.initialize()` — the inner sandbox's lifecycle is managed separately.\n\n**`cleanup()`:** `git worktree remove --force {path}`, emit `WorktreeRemoved`. Does NOT call `inner.cleanup()`.\n\n**`working_directory()`:** Returns `config.worktree_path`.\n\n**`exec_command()`:** Defaults `working_dir` to `config.worktree_path` when `None`, delegates to inner.\n\n**All other Sandbox methods:** Delegate to inner. Must be a manual `impl Sandbox` block (can't use `delegate_sandbox!` since it generates `initialize`/`cleanup`/`working_directory`/`exec_command` which we need to override).\n\nAll interpolated values in git commands use `shell_quote()`.\n\n### Step 2: Register module and re-exports\n\n- `lib/crates/fabro-sandbox/src/lib.rs`: Add `pub mod worktree;` and `pub use worktree::WorktreeSandbox;`\n- `lib/crates/fabro-agent/src/sandbox.rs`: Add re-export of `WorktreeSandbox`\n\n### Step 3: Unit tests for WorktreeSandbox\n\nIn `worktree.rs` `#[cfg(test)]` module, using `MockSandbox`:\n\n- `initialize()` issues correct git commands (branch, worktree remove, worktree add, reset) and emits events\n- `skip_branch_creation` skips branch + reset, only does worktree add\n- `cleanup()` issues `worktree remove` and emits `WorktreeRemoved`\n- `working_directory()` returns worktree path\n- `exec_command()` with `None` working_dir defaults to worktree path\n- `exec_command()` with explicit working_dir passes it through\n- `initialize()` propagates errors on non-zero exit\n\n**MockSandbox enhancement:** Add `captured_commands: Mutex>` field to `test_support.rs` to capture the sequence of `exec_command` calls (current `captured_command` only stores the last one). Append to vec in `exec_command()` impl.\n\n### Step 4: Refactor parallel.rs\n\n- **Remove** the private `WorktreeSandbox` struct (lines 28-126) and `use fabro_agent::LocalSandbox`\n- **Replace** the inline git setup loop (lines 361-450) with:\n - Construct `WorktreeConfig` with branch name, base SHA, worktree path\n - Create `WorktreeSandbox::new(Arc::clone(&services.sandbox), config)`\n - Wire event callback to bridge `WorktreeEvent` → `WorkflowRunEvent`\n - Call `initialize().await`\n- This eliminates the `if services.sandbox.is_remote()` branch (lines 442-449) — `WorktreeSandbox` works the same for any inner sandbox\n- **Cleanup loop** (lines 659-668): Keep calling `git_remove_worktree()` on the parent sandbox (the `WorktreeSandbox` Arc is consumed by the spawned task and dropped). Alternatively, could store the sandbox Arc in `BranchResult` and call `.cleanup()`, but the current approach is simpler.\n\n### Step 5: Refactor run.rs — new runs\n\nReplace `setup_worktree()` call (lines 830-845) + separate `LocalSandbox` construction with:\n\n```\nif workdir_strategy == LocalWorktree:\n base_sha = git::head_sha()\n branch_name = \"fabro/run/{run_id}\"\n inner = Arc::new(LocalSandbox::new(original_cwd))\n wt_sandbox = WorktreeSandbox::new(inner, WorktreeConfig { ... })\n wt_sandbox.set_event_callback(bridge to WorkflowRunEvent)\n wt_sandbox.initialize().await\n std::env::set_current_dir(&worktree_path) // stays in CLI, not in sandbox\n sandbox = Arc::new(wt_sandbox)\n // store base_sha, branch_name for RunConfig\n```\n\n**Delete** the `setup_worktree()` function (lines 1696-1714) — its logic is absorbed above.\n\n`std::env::set_current_dir()` stays in `run.rs` — it's a process-global side effect that belongs to the CLI.\n\n### Step 6: Refactor run.rs — resume (run_from_branch)\n\nReplace worktree re-attachment (lines 1810-1822) with:\n\n```\ninner = Arc::new(LocalSandbox::new(original_cwd))\nwt_sandbox = WorktreeSandbox::new(inner, WorktreeConfig {\n branch_name: run_branch,\n base_sha: base_sha.unwrap_or_default(),\n worktree_path: wt_str,\n skip_branch_creation: true, // branch already exists\n})\nwt_sandbox.initialize().await\nstd::env::set_current_dir(&wt)\n```\n\n### Step 7: Verify\n\n- `cargo build --workspace`\n- `cargo test --workspace`\n- `cargo clippy --workspace -- -D warnings`\n- Manual: `fabro run` with worktree mode enabled on a local workflow\n- Manual: `fabro run --run-branch` to test resume path\n\n## Files to modify\n\n| File | Change |\n|---|---|\n| `lib/crates/fabro-sandbox/src/worktree.rs` | **New** — WorktreeSandbox, WorktreeConfig, WorktreeEvent, impl Sandbox, tests |\n| `lib/crates/fabro-sandbox/src/lib.rs` | Add module + re-export |\n| `lib/crates/fabro-sandbox/src/test_support.rs` | Add `captured_commands: Mutex>` to MockSandbox |\n| `lib/crates/fabro-agent/src/sandbox.rs` | Add WorktreeSandbox re-export |\n| `lib/crates/fabro-workflows/src/handler/parallel.rs` | Remove old WorktreeSandbox, use new one |\n| `lib/crates/fabro-cli/src/commands/run.rs` | Replace setup_worktree + run_from_branch worktree logic |\n\n## Functions that become removable\n\n| Function | Location | Reason |\n|---|---|---|\n| `setup_worktree()` | `run.rs:1696` | Logic absorbed into WorktreeSandbox |\n| Old `WorktreeSandbox` struct | `parallel.rs:28-126` | Replaced by shared WorktreeSandbox |\n\nEngine git helpers (`git_add_worktree`, `git_remove_worktree`, etc. in `engine.rs`) stay — still used by parallel cleanup and potentially other callers. Sync git helpers in `git.rs` also stay.\n", + "current.preamble": "Goal: # Unified WorktreeSandbox\n\n## Context\n\nWorktree management is currently split across two locations with duplicated logic:\n\n1. **Parallel branches** (`parallel.rs`): Inline git commands in `ParallelHandler::execute()`, a thin `WorktreeSandbox` decorator, and separate local/remote code paths\n2. **Top-level CLI run** (`run.rs`): A `setup_worktree()` function using synchronous git helpers, with the worktree path fed into a plain `LocalSandbox`\n\nThe goal is a single `WorktreeSandbox` type that wraps any `Arc`, manages the worktree lifecycle in `initialize()`/`cleanup()`, and eliminates the local/remote branching.\n\n## Plan\n\n### Step 1: Create `WorktreeSandbox` in fabro-sandbox\n\n**New file:** `lib/crates/fabro-sandbox/src/worktree.rs`\n\nDefine:\n\n```rust\npub enum WorktreeEvent {\n BranchCreated { branch: String, sha: String },\n WorktreeAdded { path: String, branch: String },\n WorktreeRemoved { path: String },\n Reset { sha: String },\n}\n\npub type WorktreeEventCallback = Arc;\n\npub struct WorktreeConfig {\n pub branch_name: String,\n pub base_sha: String,\n pub worktree_path: String,\n /// Skip branch creation and reset (for resume, where branch already exists).\n pub skip_branch_creation: bool,\n}\n\npub struct WorktreeSandbox {\n inner: Arc,\n config: WorktreeConfig,\n event_callback: Option,\n}\n```\n\n**Constructor + getters:** `new(inner, config)`, `set_event_callback()`, `branch_name()`, `base_sha()`, `worktree_path()`\n\n**`initialize()`:**\n1. If `!skip_branch_creation`: `git branch --force {branch_name} {base_sha}` via `inner.exec_command()`, emit `BranchCreated`\n2. `git worktree remove --force {path}` (best-effort), then `git worktree add {path} {branch}`, emit `WorktreeAdded`\n3. If `!skip_branch_creation`: `git reset --hard {base_sha}` in worktree dir, emit `Reset`\n\nDoes NOT call `inner.initialize()` — the inner sandbox's lifecycle is managed separately.\n\n**`cleanup()`:** `git worktree remove --force {path}`, emit `WorktreeRemoved`. Does NOT call `inner.cleanup()`.\n\n**`working_directory()`:** Returns `config.worktree_path`.\n\n**`exec_command()`:** Defaults `working_dir` to `config.worktree_path` when `None`, delegates to inner.\n\n**All other Sandbox methods:** Delegate to inner. Must be a manual `impl Sandbox` block (can't use `delegate_sandbox!` since it generates `initialize`/`cleanup`/`working_directory`/`exec_command` which we need to override).\n\nAll interpolated values in git commands use `shell_quote()`.\n\n### Step 2: Register module and re-exports\n\n- `lib/crates/fabro-sandbox/src/lib.rs`: Add `pub mod worktree;` and `pub use worktree::WorktreeSandbox;`\n- `lib/crates/fabro-agent/src/sandbox.rs`: Add re-export of `WorktreeSandbox`\n\n### Step 3: Unit tests for WorktreeSandbox\n\nIn `worktree.rs` `#[cfg(test)]` module, using `MockSandbox`:\n\n- `initialize()` issues correct git commands (branch, worktree remove, worktree add, reset) and emits events\n- `skip_branch_creation` skips branch + reset, only does worktree add\n- `cleanup()` issues `worktree remove` and emits `WorktreeRemoved`\n- `working_directory()` returns worktree path\n- `exec_command()` with `None` working_dir defaults to worktree path\n- `exec_command()` with explicit working_dir passes it through\n- `initialize()` propagates errors on non-zero exit\n\n**MockSandbox enhancement:** Add `captured_commands: Mutex>` field to `test_support.rs` to capture the sequence of `exec_command` calls (current `captured_command` only stores the last one). Append to vec in `exec_command()` impl.\n\n### Step 4: Refactor parallel.rs\n\n- **Remove** the private `WorktreeSandbox` struct (lines 28-126) and `use fabro_agent::LocalSandbox`\n- **Replace** the inline git setup loop (lines 361-450) with:\n - Construct `WorktreeConfig` with branch name, base SHA, worktree path\n - Create `WorktreeSandbox::new(Arc::clone(&services.sandbox), config)`\n - Wire event callback to bridge `WorktreeEvent` → `WorkflowRunEvent`\n - Call `initialize().await`\n- This eliminates the `if services.sandbox.is_remote()` branch (lines 442-449) — `WorktreeSandbox` works the same for any inner sandbox\n- **Cleanup loop** (lines 659-668): Keep calling `git_remove_worktree()` on the parent sandbox (the `WorktreeSandbox` Arc is consumed by the spawned task and dropped). Alternatively, could store the sandbox Arc in `BranchResult` and call `.cleanup()`, but the current approach is simpler.\n\n### Step 5: Refactor run.rs — new runs\n\nReplace `setup_worktree()` call (lines 830-845) + separate `LocalSandbox` construction with:\n\n```\nif workdir_strategy == LocalWorktree:\n base_sha = git::head_sha()\n branch_name = \"fabro/run/{run_id}\"\n inner = Arc::new(LocalSandbox::new(original_cwd))\n wt_sandbox = WorktreeSandbox::new(inner, WorktreeConfig { ... })\n wt_sandbox.set_event_callback(bridge to WorkflowRunEvent)\n wt_sandbox.initialize().await\n std::env::set_current_dir(&worktree_path) // stays in CLI, not in sandbox\n sandbox = Arc::new(wt_sandbox)\n // store base_sha, branch_name for RunConfig\n```\n\n**Delete** the `setup_worktree()` function (lines 1696-1714) — its logic is absorbed above.\n\n`std::env::set_current_dir()` stays in `run.rs` — it's a process-global side effect that belongs to the CLI.\n\n### Step 6: Refactor run.rs — resume (run_from_branch)\n\nReplace worktree re-attachment (lines 1810-1822) with:\n\n```\ninner = Arc::new(LocalSandbox::new(original_cwd))\nwt_sandbox = WorktreeSandbox::new(inner, WorktreeConfig {\n branch_name: run_branch,\n base_sha: base_sha.unwrap_or_default(),\n worktree_path: wt_str,\n skip_branch_creation: true, // branch already exists\n})\nwt_sandbox.initialize().await\nstd::env::set_current_dir(&wt)\n```\n\n### Step 7: Verify\n\n- `cargo build --workspace`\n- `cargo test --workspace`\n- `cargo clippy --workspace -- -D warnings`\n- Manual: `fabro run` with worktree mode enabled on a local workflow\n- Manual: `fabro run --run-branch` to test resume path\n\n## Files to modify\n\n| File | Change |\n|---|---|\n| `lib/crates/fabro-sandbox/src/worktree.rs` | **New** — WorktreeSandbox, WorktreeConfig, WorktreeEvent, impl Sandbox, tests |\n| `lib/crates/fabro-sandbox/src/lib.rs` | Add module + re-export |\n| `lib/crates/fabro-sandbox/src/test_support.rs` | Add `captured_commands: Mutex>` to MockSandbox |\n| `lib/crates/fabro-agent/src/sandbox.rs` | Add WorktreeSandbox re-export |\n| `lib/crates/fabro-workflows/src/handler/parallel.rs` | Remove old WorktreeSandbox, use new one |\n| `lib/crates/fabro-cli/src/commands/run.rs` | Replace setup_worktree + run_from_branch worktree logic |\n\n## Functions that become removable\n\n| Function | Location | Reason |\n|---|---|---|\n| `setup_worktree()` | `run.rs:1696` | Logic absorbed into WorktreeSandbox |\n| Old `WorktreeSandbox` struct | `parallel.rs:28-126` | Replaced by shared WorktreeSandbox |\n\nEngine git helpers (`git_add_worktree`, `git_remove_worktree`, etc. in `engine.rs`) stay — still used by parallel cleanup and potentially other callers. Sync git helpers in `git.rs` also stay.\n\n", + "current_node": "toolchain" + }, + "logs": [], + "node_outcomes": { + "toolchain": { + "status": "success", + "context_updates": { + "command.output": "cargo 1.94.0 (85eff7c80 2026-01-15)\n", + "command.stderr": "" + }, + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "duration_ms": 61 + }, + "start": { + "status": "success", + "duration_ms": 0 + } + }, + "next_node_id": "preflight_compile", + "node_visits": { + "toolchain": 1, + "start": 1 + } +} \ No newline at end of file diff --git a/nodes/start/status.json b/nodes/start/status.json new file mode 100644 index 000000000..64041059b --- /dev/null +++ b/nodes/start/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": null, + "failure_reason": null, + "timestamp": "2026-03-20T01:05:04.310505+00:00" +} \ No newline at end of file diff --git a/nodes/toolchain/script_invocation.json b/nodes/toolchain/script_invocation.json new file mode 100644 index 000000000..d68c414c4 --- /dev/null +++ b/nodes/toolchain/script_invocation.json @@ -0,0 +1,5 @@ +{ + "command": "command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "language": "shell", + "timeout_ms": null +} \ No newline at end of file diff --git a/nodes/toolchain/script_timing.json b/nodes/toolchain/script_timing.json new file mode 100644 index 000000000..3a14d9c6a --- /dev/null +++ b/nodes/toolchain/script_timing.json @@ -0,0 +1,5 @@ +{ + "duration_ms": 60, + "exit_code": 0, + "timed_out": false +} \ No newline at end of file diff --git a/nodes/toolchain/status.json b/nodes/toolchain/status.json new file mode 100644 index 000000000..4cc8c34ee --- /dev/null +++ b/nodes/toolchain/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "failure_reason": null, + "timestamp": "2026-03-20T01:05:04.381053+00:00" +} \ No newline at end of file